Branch Target

Checks a merge or deploy target against your branch rules, so a feature branch doesn't land in the wrong base.

StopsFeature branch merged into wrong base

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/branch-target.json

The files land in your project. They're yours to edit.

Usage

branch-target-usage.tsx
import { BranchTarget } from "@/components/ui/branch-target"

<BranchTarget
  head="feature/otp-login"
  branches={branches}
  defaultValue="main"
  forkedFrom="develop"
  headCommits={3}
  onIssuesChange={(_, { blocking }) => setBlocked(blocking)}
/>

What it catches

  • A target the rules don't allow for this head, with a one-tap switch to an allowed one
  • Commits the merge would bring in that are not the head's own (wrong fork point)
  • A head that is behind its target
  • Targeting the head branch itself

Props

BranchTarget props
PropTypeDefault
head* (required)

The branch being merged or deployed.

string—
branches* (required)

Possible targets with their ahead/behind counts.

BranchInfo[]—
value

Selected target (controlled).

string—
defaultValue

Selected target when uncontrolled.

string—
onValueChange

Called with the newly chosen target branch.

(target: string) => void—
rules

Allowed head → target pairs. Defaults to git-flow.

TargetRule[]DEFAULT_TARGET_RULES
forkedFrom

The branch the head was cut from, if known. Enables the "commits that aren't yours" check.

string—
headCommits

Commits that are the head's own work.

number—
mode

Picks the copy: "merge" for pull requests, "deploy" for environments.

"merge" | "deploy""merge"
onUpdate

Brings the head up to date with the target. May return a promise; shows a pending state.

(target: string) => void | Promise<void>—
label

Visible label for the target picker.

stringmode === "merge" ? "Merge into" : "Deploy to"
summaryText

Readback under the picker. `{head}`, `{target}`, `{count}` and `{commits}` are filled in.

stringmode === "merge" ? "Merges {commits} from {head} into {ta…
messages

Warning text per issue. `{head}`, `{target}`, `{count}`, `{commits}` ("3 commits"), `{allowed}`, `{forkedFrom}`, `{verb}` are filled in.

Partial<Record<TargetIssueKind, string>>—
switchLabel

Label of the one-tap fix. `{suggestion}` is filled in.

string"Switch to {suggestion}"
updateLabel

`{head}` and `{target}` are filled in.

stringmode === "merge" ? "Update branch" : "Update {head}"
updatingLabel

Update button text while onUpdate runs.

string"Updating…"
protectedLabel

Badge text for protected branches.

string"protected"
behindLabel

How far the head is behind a target, in the picker. `{head}`, `{count}` and `{commits}` are filled in.

stringmode === "merge" ? "{head} is {commits} behind" : "{commi…
onIssuesChange

Called whenever the issues for the selected target change, so a form can block its submit on `blocking`.

(issues: TargetIssue[], state: { blocking: boolean; target: string }) => void—

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Native <select> with a <label>, aria-invalid when blocked and aria-describedby to its hint
  • The hint is aria-live="polite"
  • Update uses aria-disabled while updating

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Enforce branch protection and allowed merge targets in your Git host; this picker is a guide, not a protection rule.

  • Uses the branch rules, commit lists and fork point you pass; it does not query Git
  • Wildcard rules are simple * patterns; the first matching rule wins

What it stops

Feature branch merged into wrong base

Also known as

base branch · pull request · merge request · git flow · deploy target · main · develop · update branch

Field type

Dependencies

Pairs well with