component-family-consistency
Buttons, inputs, badges and pills share radius, colour logic, shadow, border and spacing.
Skill by dembrandt
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/dembrandt-component-family-consistency
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: frontend
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/component-family-consistency
What's inside
- Skill name
component-family-consistency- Description
- Buttons, inputs, badges and pills share radius, colour logic, shadow, border and spacing. Use when building or reviewing a component library.
- License
- MIT
- Metadata
- priority: 8
- Instructions
- 396 lines in SKILL.md · view on GitHub
Files (1)
- SKILL.md
SKILL.md (first lines)
# Component Family Consistency
Every interactive component in a product — buttons, inputs, selects, checkboxes, radio buttons, pills, badges, tags, calendars, date pickers, sliders, toggles — belongs to the same visual family. They share a common design DNA. A user should be able to look at any component and feel that it belongs to the same product as every other component.
When components are designed in isolation without shared tokens, the product feels assembled from parts rather than built as a whole.
## Reuse Before You Build a New Component
Before creating any component, **audit what already exists** — a new-from-scratch component is another mouth to feed: another entry in the family that must stay consistent (radius, height, states, motion) and another thing to maintain. Building fresh should be the last resort, not the first move. Work down this order:
1. **Is there already a component that does this?** Use it as-is. If it *almost* fits, extend it with a prop or variant rather than cloning it — one flexible `Button` beats `PrimaryButton`, `BigButton`, and `CtaButton` living in parallel.
2. **Is there something close in the codebase you can generalise?** Often a one-off was built inline for a single screen. If a small change would make it generic — lift it into the shared library, parameterise the hard-coded bits (label, colour, size via props/tokens), drop the screen-specific assumptions — do that instead of writing a second near-identical thing.
3. **Only build new when nothing existing fits and nothing can be reasonably generalised** — and when you do, build it *from the shared DNA below* so it joins the family cleanly.
Parallel one-offs — three near-identical buttons, two cards with different radius — are how a design system drifts. Before adding a component, ask: *does this exist, or is it one refactor from existing?*
The inverse also holds: a visual treatment that appears independently in 2–3 places has earned promotion — name it and make it a shared component or token before a fourth copy appears. And when pages from different design eras disagree, migrate old toward new: the newest components are the best evidence of current intent, but confirm before deprecating a style — see [`generate-ui-from-brand`](../generate-ui-from-brand/SKILL.md) for the consolidation pass.
> **Find the inconsistency automatically (dembrandt engine, optional).** Spotting where a live product has already diverged — five near-identical button radii, three greys that should be one — is tedious by eye. `get_findings` runs a design-system lint over a real extraction and reports consistency and duplication issues to consolidate. (And `compute_drift` scores how far two extractions have drifted apart — e.g. this product vs. its reference, or before vs. after a cleanup.) Use them to audit an existing product before deciding what to reuse. See [`extract-design`](../extract-design/SKILL.md).
## The Shared DNA
Define these tokens once. Every component inherits from them.
### Border-Radius
All interactive components use the same base radius token. Variations are derived, not invented.
```css
--radius-base: 8px; /* buttons, inputs, selects */
--radius-sm: 4px; /* checkboxes, small badges */
--radius-lg: 12px; /* cards, modals, large panels */
--radius-full: 9999px; /* pills, tags, avatar chips */
```
A button and an input on the same form must have the same radius. A pill is always `--radius-full`. A badge is `--radius-sm` or `--radius-full` depending on brand tone — but consistent across all badges.
**Nested corners are concentric.** When one rounded box sits inside another, the outer radius equals the inner radius plus the gap between them. A card with `12px` padding around an `8px` button needs `20px`, not `12px`. Get this wrong and the corners run at different curvatures a few pixels apart — nobody names it, everybody sees it.
```css
.card {
padding: var(--space-3); /* 12px */
border-radius: calc(var(--radius-base) + var(--space-3)); /* 8 + 12 = 20px */
}
```
Derive it with `calc()` rather than hardcoding the sum, so the corner stays correct when either token moves.
### Border Style
Borders across all form components and containers should use a highly restricted set of tokens.
**The 2-Step Rule:** Limit border widths to at most two options (e.g., `1px` and `4px`, or `1px` and `8px`). Do not use an incremental scale like `1px, 2px, 3px, 4px...`. A limited choice makes the hierarchy clear and the product feel intentional.
```css
--border-width-thin: 1px; /* Default for inputs, cards, dividers */
--border-width-thick: 4px; /* Featured items, bold accents, active indicators */
--border-color: var(--color-border);
--border-color-focus: var(--color-primary);
--border-color-error: var(--color-error);
```
An input border and a select border are identical at rest. Focus state uses `--border-color-focus` everywhere. Error state uses `--border-color-error` everywhere.
### Spacing and Height
Components at the same visual scale share height and internal padding.
```css
/* Default (md) size */
--component-height-md: 2.5rem; /* 40px */
--component-padding-x-md: 0.75rem; /* 12px */
--component-padding-y-md: 0.5rem; /* 8px */
/* Small */
--component-height-sm: 2rem; /* 32px */
--component-padding-x-sm: 0.5rem; /* 8px */
--component-padding-y-sm: 0.375rem; /* 6px */
/* Large */
--component-height-lg: 3rem; /* 48px */
--component-padding-x-lg: 1rem; /* 16px */
--component-padding-y-lg: 0.625rem; /* 10px */
```
Heights and text padding are in rem so a control still contains its label when the user enlarges text; borders and shadows stay in px. See [[sizing-units]].
A button and an input placed next to each other must be the same height. This is not cosmetic — mismatched heights break form layouts and signal disorder.
**Set the height, do not derive it.** A control sized only by padding has a height of line-height + padding + border, so two controls in one row drift apart whenever any of those three differ:
- an outlined variant beside a borderless one is 2px taller,
- a fluid or clamped font size changes the line box at some viewports and not others,
- an item whose content is an avatar or icon rather than text has a different intrinsic height.
Give every control on a line the same explicit height and centre its content. Set it as `min-height`, so the shape holds at rest and yields rather than clips when the content is larger than you planned.
**Argue this at the box model, not at the pixels.** Whether a border is part of the height is `box-sizing`, and naming that is what ends the discussion — a row debated in nudges never closes, because every participant is describing a different symptom of the same declaration. Under `content-box` the border is added to the height, so an outlined control is 2px taller than its ghost sibling and, once centred, sits 1px off it. Under `box-sizing: border-box` — the default in Tailwind and most resets — the border is absorbed into the height, so the boxes match and a variant can gain or lose its border without moving anything.
`border-box` moves the problem inward rather than removing it: the border now eats 1px of interior at the top and bottom, so a control whose content is not centred, or whose remaining interior is an odd number of pixels, still loses a rounding to one side. That is the 1px nobody can grep for. The fix is to keep the border in the box at all times — `border: 1px solid transparent` on the borderless variants — so the interior is identical whether or not a border is drawn.
**A derived height is also a defect you cannot search for.** An explicit height is one token you can grep and diff. A derived one is an emergent property of three separate declarations, so a row can be wrong in one control out of eight and no query finds it: in utility-class codebases the same padding appears in different orders (`rounded-md px-3 py-2` and `rounded-md border transition-colors px-3 py-2`), and a find-and-replace fixes some of them and silently skips the rest. Each miss is 2px, invisible on its own, and the reason the row still looks broken after you "fixed" it.
#### The One Way: One Class Owns the Row
Repeating the same values across siblings is how the row drifts, because every later edit has to find every copy. Declare them once instead. Every item in a row uses one shared class; that class owns the box, the content slot, and every state; an instance may set colour and nothing else.
```css
.control {
box-sizing: border-box;
height: var(--control-h); /* set, never derived */
display: inline-flex;
align-items: center;
gap: var(--control-gap);
padding-inline: var(--control-px); /* including its responsive steps */
border: 1px solid transparent; /* borderless variants keep the border, transparent */
border-radius: var(--radius-base);
font-size: var(--control-font); /* declared here, not on the label inside */
cursor: pointer; /* browsers give `button` cursor: default */
}From dembrandt/dembrandt-skills, MIT.
More from dembrandt/dembrandt-skills
- 0.785score
algorithmic-color-palette
dembrandt
Derive a full UI palette from one or two brand colours — state variants, brand-tinted greys, semantic colours.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
sticky-and-fixed-elements
dembrandt
Sticky headers, bottom toolbars, floating action buttons and pinned table headers.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
authentic-product-representation
dembrandt
Product visuals must show the real product with real data.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
brand-visual-language
dembrandt
Shape language across type, radius and icons carries brand personality.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
app-shell
dembrandt
Top bar, sidebar rail, app launcher, tenant and environment cue, status bar.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
color-mode-and-theme
dembrandt
Light, dark or both, and when a theme selector is worth it.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
data-display-and-selection
dembrandt
View modes, row selection, mass actions, sorting, filters, search and table patterns.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
domain-expert-configuration
dembrandt
Settings for experts who are not developers — domain language, sane defaults, grouping by professional concept.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
elevation-and-depth
dembrandt
Shadows and layering that lift elements above the surface.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
extract-design
dembrandt
Extract colours, typography, spacing, components and W3C design tokens from a live website with Dembrandt.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
button-states
dembrandt
Rest, hover, active, focus, disabled and loading states, derived from the base colour.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
generate-ui-from-brand
dembrandt
Pipeline from a URL or DESIGN.md to tokens, decisions and a UI spec.
SkillAFreshness: A★ 160Claude Code +9
Signals
- Stars
- 160
- Forks
- 20
- Open issues
- 0
- Last commit (this folder)
- Oct 8, 2026 (19 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/dembrandt/dembrandt-skills
- Website
- www.dembrandt.com/skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from dembrandt
- 0.792score
Dembrandt
dembrandt
Design tokens from any live site: colours, type, spacing, components, drift against a baseline.
ConnectorAFreshness: ALocal★ 3.8KClaude Code +5 - 0.785score
app-shell
dembrandt
Top bar, sidebar rail, app launcher, tenant and environment cue, status bar.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
authentic-product-representation
dembrandt
Product visuals must show the real product with real data.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
brand-visual-language
dembrandt
Shape language across type, radius and icons carries brand personality.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
button-states
dembrandt
Rest, hover, active, focus, disabled and loading states, derived from the base colour.
SkillAFreshness: A★ 160Claude Code +9 - 0.785score
algorithmic-color-palette
dembrandt
Derive a full UI palette from one or two brand colours — state variants, brand-tinted greys, semantic colours.
SkillAFreshness: A★ 160Claude Code +9
Cite this page
component-family-consistency skill. Agent Mart. https://agentmart.dev/skills/dembrandt-component-family-consistency (last updated 2026-10-24)