| Field | Value |
|---|
| ID | FRD-041 |
| Owner | David Holmes |
| Status | Complete ✅ |
| Priority | P1 — Developer Experience |
| Size | M (Refactor) |
| Target Release | v2.0.0 |
| Last Updated | 2026-05-26 |
Document Summary
Audit and fix every Storybook story’s controls (argTypes) to use the correct control type, include descriptions, and hide internal props. Two-option props must use boolean controls; short enums (2-5 values) must use inline-radio; numeric props must use range with sensible min/max/step; event handler props must have control: false; internal/private props must be hidden from the controls panel entirely. The audit script enforces these rules in CI.
Introduction
Overview
Storybook controls are the primary interactive mechanism for developers to explore component behavior. Poorly configured controls — wrong control types, missing descriptions, exposed internal props — make the exploration experience frustrating and confusing. This FRD standardizes control quality across all story files.
Goals
- Every prop with exactly two values (true/false, on/off) uses a
boolean control.
- Every prop with 2-5 enum values uses an
inline-radio control.
- Every numeric prop uses a
range control with appropriate min, max, and step values.
- Every event handler prop (
on*) has control: false so it does not appear as a text input.
- Every internal prop (prefixed with
_ or documented as internal) is hidden via table.disable: true.
- Every visible control has a
description string in its argType.
pnpm run dx:audit enforces all rules with zero tolerance.
Non-Goals
- Adding new props to components.
- Changing component APIs or behavior.
- Custom Storybook addons for controls rendering.
- Documenting controls behavior in MDX pages (covered by FRD-040).
Scope
In Scope
| Item | Description |
|---|
| ArgTypes audit | Review every story file’s argTypes configuration against the control type rules |
| Control type fixes | Update argTypes to use correct control types per the rules |
| Description authoring | Add description strings to all visible argTypes entries |
| Internal prop hiding | Set table.disable: true on props that are internal implementation details |
| Event handler controls | Set control: false on all on* callback props |
| Audit script rules | Add control quality detection to scripts/checks/audit-story-dx.mjs |
Out of Scope
| Item | Reason |
|---|
| Custom control renderers | Standard Storybook control types are sufficient |
| Component prop refactoring | Control configuration is independent of prop design |
| Visual theming of controls panel | Out of scope for this DX effort |
Users and Pain Points
| User | Pain Point |
|---|
| Application developer | Boolean prop shows as a text input instead of a toggle — confusing and error-prone |
| Application developer | Enum prop shows as a freeform text field — no discoverability of valid values |
| Application developer | Internal props clutter the controls panel — unclear which props are part of the public API |
| Application developer | Event handler props show as text inputs — misleading since typing does nothing useful |
| Application developer | No description on controls — must read source to understand what a prop does |
Definitions
| Term | Definition |
|---|
| ArgTypes | Storybook metadata that configures how each prop appears in the controls panel |
| Inline-radio | A control type that renders radio buttons inline, ideal for short enum lists |
| Range control | A slider control for numeric values with configurable min, max, and step |
| Internal prop | A prop intended for use by the design system internally, not by consumer applications |
Current State
- Many stories rely on Storybook’s auto-inferred control types, which often choose
text for enums and object for complex types.
- Event handler props appear as editable text fields in the controls panel.
- Internal props (e.g.,
_internalVariant, ref) are visible to developers.
- Few stories have
description strings on their argTypes.
- The audit script does not currently check control type correctness.
Proposed Solution
Phase 1: Extend the audit script
Add rules to scripts/checks/audit-story-dx.mjs:
- Detect boolean props without
control: { type: "boolean" }.
- Detect short enum props without
control: { type: "inline-radio" }.
- Detect numeric props without
control: { type: "range" }.
- Detect
on* props without control: false.
- Detect props prefixed with
_ that are not hidden.
- Detect visible props without a
description.
Phase 2: Fix violations
For each story file, update argTypes to comply with the rules. This requires:
- Reading the component’s TypeScript interface to identify prop types.
- Mapping each prop to the correct control type.
- Writing a brief description for each public prop.
- Hiding internal props.
Phase 3: CI enforcement
Ensure pnpm run dx:audit blocks merge on control quality violations.
Requirements
| ID | Requirement | Priority |
|---|
| REQ-01 | Two-value props use boolean control | Must |
| REQ-02 | Short enum props (2-5 values) use inline-radio control | Must |
| REQ-03 | Numeric props use range control with min/max/step | Must |
| REQ-04 | Event handler props have control: false | Must |
| REQ-05 | Internal props are hidden via table.disable: true | Must |
| REQ-06 | All visible controls have a description | Must |
| REQ-07 | Long enum props (6+ values) use select control | Should |
| REQ-08 | Range controls have sensible defaults (e.g., step=1 for integers, step=0.1 for decimals) | Should |
Functional Requirements
| ID | Description | Acceptance |
|---|
| FR-01 | Audit script detects wrong control types | Lists file, prop name, current type, expected type |
| FR-02 | Audit script detects missing descriptions | Lists file and prop name |
| FR-03 | Audit script detects exposed internal props | Lists file and prop name |
| FR-04 | Audit script detects event handlers without control: false | Lists file and prop name |
| FR-05 | Controls panel for each component shows only public props with correct types | Visual verification on 10 sample components |
Non-Functional Requirements
| ID | Description | Target |
|---|
| NFR-01 | Audit script execution time | Under 10 seconds (combined with other DX audit rules) |
| NFR-02 | No runtime performance impact | ArgTypes are static metadata; no effect on component rendering |
API/Interface Requirements
| Interface | Requirement |
|---|
argTypes configuration | Must follow the control type mapping rules defined in this FRD |
scripts/checks/audit-story-dx.mjs | New rules: wrong-control-type, missing-description, exposed-internal-prop, event-handler-visible |
Accessibility Requirements
| ID | Requirement |
|---|
| A11Y-01 | No direct accessibility impact — this FRD targets Storybook authoring, not rendered UI |
| A11Y-02 | Control descriptions should mention accessibility implications where relevant (e.g., “Visually hides the label but keeps it available to screen readers”) |
Content and Documentation Requirements
| ID | Requirement |
|---|
| DOC-01 | Add a “Controls Style Guide” section to the Storybook contributing guide |
| DOC-02 | Document the control type mapping rules (boolean, inline-radio, range, select) in the guide |
| DOC-03 | Provide examples of well-configured argTypes for common prop patterns |
Dependencies
| Dependency | Type | Risk |
|---|
| Component TypeScript interfaces | Internal | Prop types must be readable by the audit script or documented in argTypes |
scripts/checks/audit-story-dx.mjs | Internal | Must be extended before bulk fixes begin |
| Storybook controls addon | External | Control type names must match Storybook’s expected values |
Risks and Tradeoffs
| Risk | Impact | Mitigation |
|---|
| Automated detection of prop types may be imprecise | False positives in audit | Allow // dx:ignore comments for edge cases; manual review of flagged items |
| Large number of stories to update | Slow delivery | Batch by component directory; M-size scope is manageable in 2-3 sprints |
| Range control min/max guessing | Incorrect slider bounds | Default to 0-100/step 1; component authors can override |
Open Questions
| # | Question | Status |
|---|
| 1 | Should the audit script parse TypeScript interfaces to auto-detect prop types, or rely on argTypes declarations? | Open |
| 2 | Should ref props be hidden by default? | Open |
| 3 | Should className and style props have controls or be hidden? | Open |
Acceptance Criteria
LLM Handoff Instructions
When an LLM agent picks up this FRD:
- Run
pnpm run dx:audit to get the current violation report.
- For each violating story file, read the corresponding component’s TypeScript interface (props type) to understand each prop’s type and purpose.
- Apply the control type mapping:
boolean type or exactly 2 string literals → control: { type: "boolean" }
- 2-5 string literal union →
control: { type: "inline-radio" }, options: [...]
number type → control: { type: "range", min: 0, max: 100, step: 1 }
on* callback → control: false
_-prefixed or internal → table: { disable: true }
- Write a
description for each visible prop: one sentence explaining what it does.
- After fixing a batch, run
pnpm run dx:audit and pnpm typecheck.
- Do not modify component implementations — only story files and the audit script.
Decision Log
| Date | Decision | Rationale |
|---|
| 2026-05-26 | Use inline-radio for short enums rather than radio | Inline layout is more compact and scannable for 2-5 options |
| 2026-05-26 | Require descriptions on all visible controls | Descriptions are the primary documentation surface in the controls panel |
Document History
| Date | Version | Author | Changes |
|---|
| 2026-05-26 | 0.1 | David Holmes | Initial draft |