Branch Naming
The type/kebab-case branch naming convention enforced at pre-push.
Branches in this monorepo follow a strict <type>/<kebab-case-description> shape. The check runs on git push: if the branch you are pushing does not match the pattern, the push is rejected before it ever touches the remote.
Source of truth. Branch validation lives in .husky/pre-push:5-33. If this page and that script disagree, the script wins.
The pattern
<type>/<kebab-case-description>
type— one of the allowed prefixes below. Same list thatcommitlintuses for commit types, minus the ones that never make sense for a branch (style,perf,build,revert).description— lower-case letters and digits, separated by hyphens. No spaces, no underscores, no slashes, no camelCase. Must start with a letter or digit.
The exact regex enforced by the hook is:
^(feat|fix|chore|ci|docs|refactor|test|variant)\/[a-z0-9][a-z0-9-]*$
Allowed type prefixes
| Prefix | Use when... |
|---|---|
feat | You are building a new feature on this branch. |
fix | You are tracking down and fixing a bug. |
chore | Routine maintenance: dependency bumps, tooling tidy-ups, housekeeping that touches no public behaviour. |
ci | You are changing CI configuration — workflows, husky hooks, turbo pipelines, release automation. |
docs | You are working on documentation only — MDX pages, READMEs, concept notes. |
refactor | You are restructuring code without changing its observable behaviour. |
test | You are adding or updating tests. |
variant | You are adding or adjusting a CVA variant on a UI component in @itu/ui. |
Why the convention exists
Branch naming is not bureaucracy for its own sake — the pattern unlocks several things:
- Predictable automation hooks. CI jobs can key off the prefix:
docs/*branches skip the full test matrix,ci/*branches trigger a self-check,feat/*branches run the full suite. Without a convention, every workflow would need a manually maintained list. - CI routing and triage. When a branch fails on CI, the prefix tells the on-call reviewer what kind of change they are looking at before they open the diff. A red
chore/update-depsis triaged differently than a redfix/search-url-html-strip. - Human readability in the branch list.
git branch -ais easier to scan when every branch starts with a verb-sized prefix. You can find "all the doc branches" at a glance. - Consistency with commit types. Using the same vocabulary for branches and commits means a single mental model — not two.
- Kebab-case is URL-safe. Branch names end up in GitHub URLs, CI job names, and Slack notifications. Hyphens render cleanly everywhere; underscores and camelCase do not.
Passing examples
feat/documentation-website feat/add-playground-controls fix/search-url-html-strip fix/button-hover-state docs/contributor-workflow chore/update-deps variant/links-grid-compact refactor/extract-cva-factory test/datatable-sort-order ci/add-a11y-workflow
All of these match the pattern: allowed prefix, slash, kebab-case description starting with a letter or digit.
Failing examples
Each failure below is the actual error message the hook prints before exit 1. The message comes straight from .husky/pre-push.
Wrong prefix
feature/add-playground-controls
ERROR: Branch name 'feature/add-playground-controls' does not match the naming convention. Pattern: <type>/<kebab-case-description> Types: feat, fix, chore, ci, docs, refactor, test, variant Examples: feat/documentation-website fix/button-hover-state variant/links-grid-compact chore/update-deps
feature is not in the allowed list — use feat instead.
No slash
feat-add-playground-controls
ERROR: Branch name 'feat-add-playground-controls' does not match the naming convention. Pattern: <type>/<kebab-case-description> Types: feat, fix, chore, ci, docs, refactor, test, variant Examples: feat/documentation-website fix/button-hover-state variant/links-grid-compact chore/update-deps
The type and the description have to be separated by a slash, not a hyphen.
Upper-case or camelCase description
feat/AddPlaygroundControls
ERROR: Branch name 'feat/AddPlaygroundControls' does not match the naming convention. Pattern: <type>/<kebab-case-description> Types: feat, fix, chore, ci, docs, refactor, test, variant Examples: feat/documentation-website fix/button-hover-state variant/links-grid-compact chore/update-deps
Descriptions must be kebab-case — lower-case, hyphen-separated.
Underscores instead of hyphens
feat/add_playground_controls
ERROR: Branch name 'feat/add_playground_controls' does not match the naming convention. Pattern: <type>/<kebab-case-description> Types: feat, fix, chore, ci, docs, refactor, test, variant Examples: feat/documentation-website fix/button-hover-state variant/links-grid-compact chore/update-deps
Underscores are not allowed — use hyphens.
Exempt branches
A handful of branch names bypass the check entirely. The hook uses a case statement against the current branch and ;; skips validation when the branch matches any of these patterns:
| Pattern | Why it is exempt |
|---|---|
main | The default integration branch. Every release is cut from here — it can't be renamed to match a <type>/<desc> pattern without breaking every clone, CI workflow, and deploy pipeline in the monorepo. |
develop | Long-lived integration branch used alongside main for staged rollouts. Same rationale as main — it is a permanent fixture, not a topic branch. |
release/* | Release branches cut from main at the start of a release candidate cycle (e.g. release/2026-Q2). These are not topic branches and should not pretend to be — they are version-scoped. |
hotfix/* | Emergency fixes that branch off a release tag and merge back into both main and the release branch. hotfix is its own lifecycle; it reads clearly without being shoehorned into a type prefix. |
The exempt list is deliberately small. Everything else — including personal scratch branches, WIP spikes, and experiments — has to follow the convention.
When the hook runs
The check is part of .husky/pre-push, which fires every time you run git push. If you have never pushed a branch before, the hook runs once the branch is named and tracked — you do not need a remote to trigger it locally during git push.
If you realise mid-work that your branch is misnamed, rename it before pushing:
git branch -m old-name feat/new-name
Related
- Commit messages — the sibling convention that
commitlintenforces on every commit. - Git hooks — the full tour of husky hooks, including what
pre-pushdoes after the branch check passes.