What This Template Is For
A component spec is the definitive reference for how a single UI element works. It documents every state, variant, interaction, and edge case so designers and engineers build the same thing without ambiguity. The spec answers questions that mockups cannot: What happens when the label text is 200 characters long? What does the error state look like? What keyboard shortcuts does this support? How does it behave on a 320px screen?
Most component bugs trace back to unspecified states. The designer delivered a mockup showing the default and hover state, but nobody defined the disabled state, the loading state, the error state, or the overflow behavior. The engineer made reasonable guesses. The PM filed bugs because the guesses did not match the intended behavior. The component spec prevents this cycle by forcing every state to be explicitly defined before implementation begins.
This template works for any reusable UI element: buttons, inputs, dropdowns, modals, cards, tables, navigation items, and custom components. It pairs well with the design system documentation template for cataloging components as part of a broader system. For teams running formal handoff reviews, the design review template provides a structured checkpoint to validate the spec before engineering starts. Understanding the concept of cognitive load helps PMs make better decisions about component complexity and when to simplify.
How to Use This Template
- Start with the Component Overview. Name the component, describe its purpose in one sentence, and list where it appears in the product. This anchors the spec in real usage rather than abstract component theory.
- Draw the anatomy diagram. Label every visual element of the component: icon, label, helper text, border, background, indicator. This creates a shared naming convention between design and engineering.
- Define all states. Most interactive components have at least 6 states: default, hover, focus, active/pressed, disabled, and error. Some have loading, selected, and read-only states as well. For each state, specify the visual treatment and any behavioral changes.
- Define all variants. Size variants (small, medium, large), style variants (primary, secondary, ghost), and context variants (inline, standalone) should each be documented with their use cases.
- Specify interaction behavior: click, keyboard, touch, and screen reader interactions. Include tab order, arrow key behavior, and ARIA attributes.
- Document edge cases. What happens with very long text? What about empty data? RTL languages? High contrast mode? The edge cases section prevents the most common implementation surprises.
The Template
Component Overview
| Field | Details |
|---|---|
| Component Name | [Name as it appears in code and design system] |
| Purpose | [One sentence: what user need does this component serve?] |
| Usage Locations | [List screens/features where this component appears] |
| Related Components | [Components this extends, wraps, or is often used with] |
| Design System Status | Draft / In Review / Approved / Implemented |
Anatomy
Label every distinct visual element of the component.
┌─────────────────────────────┐
│ [Icon] [Label] [▼] │ ← Container
│ │
│ [Helper text] │ ← Supporting text
└─────────────────────────────┘
| Element | Required | Description |
|---|---|---|
| Container | Yes | [Dimensions, padding, border, background] |
| Icon | No | [Leading icon, size, color] |
| Label | Yes | [Typography token, color, truncation behavior] |
| Indicator | Yes | [Chevron, arrow, or state indicator] |
| Helper text | No | [Supporting text below the component] |
States
| State | Visual Treatment | Behavior |
|---|---|---|
| Default | [Border, background, text color] | [Clickable, awaiting interaction] |
| Hover | [Border change, background change, cursor] | [Visual feedback on pointer enter] |
| Focus | [Focus ring style, outline offset] | [Keyboard focus indicator visible] |
| Active / Pressed | [Background darkens, slight scale] | [Visual feedback during click/tap] |
| Disabled | [Opacity reduction, cursor not-allowed] | [Not interactive, skip in tab order] |
| Error | [Red border, error icon, error message] | [Validation failure state] |
| Loading | [Spinner or skeleton, reduced opacity] | [Awaiting data, not interactive] |
| Read-only | [Muted styling, no interaction affordance] | [Displays value but cannot be changed] |
Variants
| Variant | Use Case | Visual Difference |
|---|---|---|
| Size: Small | Dense layouts, tables, toolbars | [Height, padding, font size] |
| Size: Medium | Default for most forms | [Height, padding, font size] |
| Size: Large | Primary actions, onboarding | [Height, padding, font size] |
| Style: Primary | Main action, high emphasis | [Fill color, text color] |
| Style: Secondary | Supporting action, medium emphasis | [Outline style, text color] |
| Style: Ghost | Tertiary action, low emphasis | [No border, text-only] |
Interaction Behavior
Mouse / Touch:
- ☐ Click triggers [action]
- ☐ Hover shows [visual feedback]
- ☐ Long press triggers [action, if applicable]
- ☐ Drag behavior: [none / reorderable / resizable]
Keyboard:
- ☐
Tabmoves focus to/from the component - ☐
EnterorSpacetriggers the primary action - ☐
Escapecloses/dismisses (if applicable) - ☐ Arrow keys [behavior if applicable]
- ☐ Type-ahead search [if applicable]
Screen Reader:
- ☐ ARIA role: [button / combobox / listbox / etc.]
- ☐ ARIA label: [How the component is announced]
- ☐ ARIA expanded/selected/checked: [State announcements]
- ☐ Live region: [For dynamic content changes]
Responsive Behavior
| Breakpoint | Behavior |
|---|---|
| Mobile (< 640px) | [Full width, stacked layout, touch target 44px minimum] |
| Tablet (640-1024px) | [Behavior] |
| Desktop (> 1024px) | [Default behavior as specified above] |
Edge Cases
| Case | Expected Behavior |
|---|---|
| Label text exceeds container width | [Truncate with ellipsis / wrap to multiple lines / tooltip on hover] |
| Empty data / no options | [Show empty state message / disable component] |
| Very long list of items | [Virtual scrolling / max-height with scroll / search filter] |
| RTL language | [Mirror layout, preserve icon positions] |
| High contrast mode | [Borders visible, focus ring meets contrast ratio] |
| Slow network | [Loading skeleton for async data] |
Design Tokens
| Property | Token | Value |
|---|---|---|
| Border radius | --radius-md | [Value] |
| Border color (default) | --border-default | [Value] |
| Border color (focus) | --border-focus | [Value] |
| Background (default) | --bg-input | [Value] |
| Text color | --text-primary | [Value] |
| Font size | --text-sm | [Value] |
| Padding | --space-2 --space-3 | [Value] |
| Transition | --duration-fast --ease-standard | [Value] |
Filled Example: Multi-Select Dropdown
Component Overview
| Field | Details |
|---|---|
| Component Name | MultiSelect |
| Purpose | Allow users to select one or more options from a filterable dropdown list |
| Usage Locations | Filter bars, form fields (assign team members, select tags), settings pages |
| Related Components | Select (single), Combobox, Checkbox, Tag/Chip |
| Design System Status | In Review |
Anatomy
┌──────────────────────────────────────┐
│ [Tag] [Tag] [Tag] [input] [Clear ✕]│ ← Container (selected tags + input)
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ 🔍 [Search input] │ ← Dropdown header
├──────────────────────────────────────┤
│ ☑ Option A │ ← Selected option
│ ☐ Option B │ ← Unselected option
│ ☐ Option C │ ← Unselected option
│ ☑ Option D │ ← Selected option
├──────────────────────────────────────┤
│ 3 of 12 selected │ ← Footer (count)
└──────────────────────────────────────┘
| Element | Required | Description |
|---|---|---|
| Container | Yes | 40px height (medium), 8px padding, 1px border, grows to wrap tags |
| Selected tags | Conditional | Cyan pill with "x" remove button, max 3 visible + "+N more" |
| Text input | Yes | Inline input for type-ahead filtering, placeholder: "Select..." |
| Clear button | Conditional | "x" icon at right edge when 1+ items selected |
| Dropdown panel | Conditional | Opens below container, max-height 280px, scrollable |
| Search input | Yes | Pinned at top of dropdown, does not scroll with options |
| Option row | Yes | 36px height, checkbox + label, hover highlight |
| Footer | Optional | Shows "N of M selected" when list exceeds 10 items |
States
| State | Visual Treatment | Behavior |
|---|---|---|
| Default | Gray border, "Select..." placeholder | Click opens dropdown |
| Hover | Border darkens to gray-400 | Cursor changes to pointer |
| Focus | 2px cyan focus ring, offset 2px | Dropdown opens on focus (optional: only on Enter) |
| Open | Cyan border, dropdown visible, shadow-lg | Type-ahead filters options in real time |
| Disabled | 50% opacity, cursor not-allowed | No interaction. Selected values still visible but not editable |
| Error | Red border, red helper text below | "Please select at least one option" (or custom message) |
| Loading | Spinner replaces chevron, "Loading..." in dropdown | Options fetched asynchronously |
| Read-only | No border, tags displayed as static text | Values visible but not clickable |
Variants
| Variant | Use Case | Visual Difference |
|---|---|---|
| Size: Small | Table cells, dense filter bars | 32px height, 12px text, max 2 tags visible |
| Size: Medium | Standard forms | 40px height, 14px text, max 3 tags visible |
| Size: Large | Primary forms, onboarding | 48px height, 16px text, max 4 tags visible |
| Creatable | Tag management, flexible inputs | Shows "Create [typed value]" option when no match found |
Interaction Behavior
Mouse / Touch:
- ☑ Click on container opens dropdown, focuses search input
- ☑ Click on option toggles selection (checkbox updates, tag added/removed)
- ☑ Click on tag "x" removes that selection
- ☑ Click on clear button removes all selections
- ☑ Click outside dropdown closes it, preserving selections
Keyboard:
- ☑
Tabfocuses the container. SecondTabmoves past the component - ☑
EnterorSpaceon container opens dropdown - ☑ Arrow keys navigate options within the open dropdown
- ☑
EnterorSpaceon an option toggles its selection - ☑
Escapecloses dropdown without changing selections - ☑
Backspaceon empty input removes the last selected tag - ☑ Typing filters the option list (type-ahead search)
Screen Reader:
- ☑ ARIA role:
comboboxon container,listboxon dropdown,optionon each row - ☑ ARIA label: "[Field label], multi-select, N items selected"
- ☑
aria-expanded="true/false"on container reflecting dropdown state - ☑
aria-selected="true/false"on each option - ☑
aria-activedescendantupdates as arrow keys move focus within dropdown - ☑ Live region announces selection changes: "Option A selected. 3 of 12 selected."
Edge Cases
| Case | Expected Behavior |
|---|---|
| 50+ selected items | Show first 3 tags + "+47 more" badge. Tooltip on badge shows full list |
| Option label > 60 characters | Truncate with ellipsis in dropdown. Full text in tooltip on hover |
| 0 options available | Show "No options available" in dropdown body. Disable search input |
| Search returns no matches | Show "No results for '[query]'" with clear search button |
| All options selected | Show "All selected" in footer. Disable search input |
| Async load failure | Show "Failed to load options. Retry." with retry button in dropdown |
| Form submission with 0 selections | Block submit if required. Show error state with helper text |
Common Mistakes to Avoid
- Documenting only the happy path. The default and hover states are the easy ones. The bugs live in error, loading, disabled, overflow, and empty states. Spec every state explicitly. Use the usability testing process to validate that edge cases work before launch.
- Skipping keyboard behavior. Every interactive component must be fully operable by keyboard. This is not a nice-to-have. It is a WCAG requirement and a legal obligation in many markets.
- Defining variants without use cases. Three size variants and four style variants create 12 combinations. If you cannot name a real screen where each combination appears, you are building unused complexity. Start with the variants you need today.
- Using screenshots instead of specs. Screenshots show one state at one viewport width. They cannot communicate transitions, keyboard behavior, overflow handling, or responsive behavior. Screenshots are supplements to a spec, not substitutes.
- Not defining the token contract. If the spec says "gray border" instead of referencing a design token (
--border-default), the engineer will pick a gray hex value that does not match the system. Always reference tokens.
Key Takeaways
- Spec every state explicitly: default, hover, focus, active, disabled, error, loading, and read-only
- Define keyboard and screen reader behavior as first-class requirements, not afterthoughts
- Document edge cases (overflow, empty data, async failure) to prevent implementation guesswork
- Reference design tokens by name, not by raw color or size values
- Include filled examples with real product scenarios to make the spec concrete
About This Template
Created by: Tim Adair
Last Updated: 3/5/2026
Version: 1.0.0
License: Free for personal and commercial use
