Components
Reusable UI components live in the shared @repo/ui package.
Components should provide stable public APIs, remain independent of application-specific business logic, and prefer native platform semantics when possible.
Component groups
Storybook currently organizes components into two primary groups:
Primitives
Form Controls
Button is a general-purpose primitive because it is used outside forms as
well as inside them.
Form-specific controls and composition primitives live under Form Controls.
Button
The reusable Button primitive supports the following variants:
primary
secondary
outline
ghost
danger
Supported sizes include:
sm
md
lg
icon
The component supports:
- native button behavior
- refs
- disabled state
- loading state
- accessibility state through
aria-busy
Form controls
The current form-control foundation includes:
Input
Textarea
Checkbox
Radio
Switch
Select
Label
Field
Fieldset
Legend
Shared sizing
Visual form controls use the controlSize API instead of overloading native
HTML attributes such as size.
Supported values are:
sm
md
lg
Native attributes remain available when they do not conflict with the component API.
Native semantics
Components prefer native HTML behavior where possible.
Examples include:
Inputrenders a nativeinputTextarearenders a nativetextareaSelectuses a native single-select controlCheckboxandRadiouse native inputsSwitchuses a checkbox withrole="switch"Labeluses a nativelabelFieldsetandLegendpreserve native group semantics
This keeps browser behavior, keyboard interaction, form submission, and accessibility semantics aligned with the platform.
Field composition
Field provides a lightweight composition layer for individual controls.
It exports:
Field
FieldLabel
FieldDescription
FieldError
Consumers remain responsible for explicit accessibility relationships such as:
id
htmlFor
aria-describedby
aria-invalid
The component does not use context, automatic ID generation, element cloning, or client-side state to create implicit relationships.
Fieldset and Legend are used when multiple related controls form one
semantic group.
Wrapped-control class names
Controls that require an internal layout wrapper follow this convention:
Checkbox
Radio
Switch
Select
For these components, className targets the component layout root.
The underlying native element can be styled separately through:
Checkbox.inputClassName
Radio.inputClassName
Switch.inputClassName
Select.selectClassName
Components without an internal wrapper continue to apply className directly
to their native root element.
Direction
Direction is inherited from the application by default.
Components use logical CSS properties instead of physical left/right assumptions.
Controls with directional visual siblings, such as Select and Switch,
also support an explicit local dir so their visual layout remains aligned
with the native control.
The application remains responsible for setting the document-level language and direction.
Implementation conventions
Variant composition uses Class Variance Authority.
Class composition uses clsx and tailwind-merge through the shared cn()
utility.
Components avoid framework-specific dependencies and unnecessary client-side React features so the shared UI package remains compatible with server-rendered applications.
Public package exports allow consumers to import components through
@repo/ui instead of internal implementation paths.