---
name: ds-component-rules
description: Use when creating, modifying, reviewing, or refactoring React components in the design-system repository, including component APIs, props, state, context, polymorphism, memoization, refs, TypeScript types, CSS Modules, CSS Grid templating, and component tokens.
---

# Design System Component Rules

These rules govern React components in this design system. Requirement levels follow RFC 2119:

- **MUST / MUST NOT**: absolute requirement; violation blocks code review.
- **SHOULD / SHOULD NOT**: recommended requirement; exceptions need a reason in review.
- **MAY**: optional.

## 1. Core Principles

- **Single responsibility**: each component directory contains one component type; each file has one concern. Split components that take on unrelated behavior.
- **Extensibility**: behavior changes through the public API only. Props MUST extend the native attributes of the root element so consumers can pass `id`, `title`, `data-*`, ARIA, native handlers, etc.
- **Predictability**: components wrapping native elements MUST preserve native naming and behavior. Use `value`, `checked`, `defaultValue`, `defaultChecked`, `onChange`, not custom replacements like `active` or `onToggle`.
- **Compound components**: component groups that share state SHOULD accept `children` and use compound children instead of rigid `items` / `options` arrays when consumers need markup control.
- **Structural flexibility**: children in a compound group SHOULD stay independent; order SHOULD NOT affect behavior unless UI logic explicitly requires it.
- **Clean API**: child-only data and props MUST belong to the child API, not the parent. Parent components MUST NOT know about child internals such as indexes, icon arrays, or child event handlers.
- **State encapsulation**: shared group data flows through Context API. Parents distribute common values through context; children MAY override via their own props.

## 2. Naming

- Component names MUST be semantic nouns that describe UI role, not appearance or platform: `Button`, not `BlueButton`; `MenuItem`, not `MobileMenuItem`.
- Code names MUST match Figma names. If the Figma name is wrong, align on the Figma rename first.
- Component names MUST be PascalCase, English, and Latin-only.
- Child components MUST use the parent prefix: `List`, `ListItem`, `ListItemIcon`, `ListItemTitle`; never bare `Item` or `Icon`.

## 3. File Structure

Every component lives in its own directory. File names match the component name and concern:

```text
ComponentName/
  ComponentName.tsx
  ComponentName.types.ts
  ComponentName.consts.ts
  ComponentName.module.css
  ComponentName.test.tsx
  context/
    ComponentNameContext.tsx
    useComponentNameContext.ts
    getComponentNameContext.ts  # generic contexts only
  docs/
  utils/
  index.ts
```

- `ComponentName.tsx`: component logic and markup only.
- `ComponentName.types.ts`: interfaces and types only.
- `ComponentName.consts.ts`: static data, configs, constants.
- `ComponentName.module.css`: CSS Modules styles.
- `ComponentName.test.tsx`: functional tests.
- `context/`: context object, context hook, and generic context factory when needed.
- `utils/`: helper functions named with verb prefixes, e.g. `getWidth`, `formatDate`.
- `index.ts`: public API exports.

## 4. State

- Controlled form state MUST use native props: `value` / `checked` and native event handlers such as `onChange`.
- Form components MUST support uncontrolled mode through `defaultValue` / `defaultChecked`.
- Non-form components MAY support uncontrolled mode when justified, using `default` + controlled prop name, e.g. `defaultOpen`.
- Hybrid state MUST switch between controlled and uncontrolled based on controlled prop presence. Controlled state takes priority over default state.
- Components MUST use `useControlled` from `@utils/useControlled` for controlled/uncontrolled switching. Manual switching logic is forbidden.
- Data priority when reading a prop also available from context is: prop, context, default.

```tsx
const size = sizeProp ?? context?.size ?? 'm';
```

## 5. Context API

- Context is the main mechanism for implicit data exchange inside compound groups.
- Context objects MUST be named `{ComponentName}Context` and live outside the component file.
- Context objects MUST be initialized with `null`; do not provide unsafe default values to `createContext`.
- Access context ONLY through `use{ComponentName}Context`; do not call raw `use(...)` / `useContext(...)` in consumers.
- Custom context hooks MUST consume context via React 19's `use(...)` (not the legacy `useContext`).
- Providers MUST use the React 19 short syntax `<{ComponentName}Context value={...}>`, not `<{ComponentName}Context.Provider>`.
- Provider `value` MUST be memoized with `useMemo`.
- Consumers MUST handle missing provider values instead of relying on unsafe defaults.
- Context values MUST be named `{ComponentName}ContextValue`.
- If a generic component passes generic data through context, the context value type MUST also be generic.
- A generic context MUST be created through `get{ComponentName}Context` so the generic parameter is preserved.
- `use{ComponentName}Context` for generic context MUST cast to the generic context value type:

```tsx
const useListContext = <T,>() => {
  return use(ListContext) as ListContextValue<T> | null;
};
```

## 6. Polymorphic Components

- A component is polymorphic when it can change its root DOM element or root component.
- The controlling prop MUST be named `component`, not `as`, `tag`, or another alias.
- The JSX variable MUST be named `Component` with a capital `C`.
- Changing the root element MUST NOT change visual appearance.
- The `component` prop type MUST be restricted to semantically valid tags/components, not unrestricted `ElementType`.
- Polymorphic props MUST extend native attributes for the selected root element.
- Polymorphic ref types MUST dynamically match the selected root element.
- Polymorphic components are generic components and follow all generic typing and memo rules. Refs are passed as a regular `ref` prop (no `forwardRef` wrapper).

## 7. Memoization

- Use `memo` when required props are primitive and shallow comparison is accurate.
- Do NOT use `memo` when required props are objects, arrays, or `ReactElement` values where reference comparison is usually noise.
- Use `memo` for context consumers to avoid parent rerenders when context value has not changed.
- Generic components MUST use `memo` from `@utils/memo`, not React's `memo`. React's `memo` erases the generic type parameter; the local helper preserves it via a typed cast.
- Do NOT wrap a generic component with `React.memo` directly.

## 8. Refs

- `forwardRef` is REMOVED in this codebase. React 19 deprecated it; use ref-as-prop.
- Wrapper components MUST accept `ref` as a regular prop and pass it to the first valid root DOM node or root component.
- The destructured prop name MUST be `ref`.
- `{ComponentName}Props` MUST declare `ref?: Ref<{Element}>` (or `ref?: Ref<{ComponentName}Ref<T>>` for polymorphic/generic components) explicitly. Add `Ref` to the type imports from `react`.
- Do NOT import `forwardRef` from `react` or `@utils/forwardRef` (the latter has been deleted).
- Do NOT import `ForwardedRef`. Use `Ref<T>` for prop typing.
- If a component has addressable internal elements, expose refs through props named `{elementName}Ref`.
- Ref types MUST be named `{ComponentName}Ref` for component refs and `{ElementName}Ref` for element refs.
- Ref types MUST match their DOM elements. Polymorphic refs MUST match the selected `component` type.

```tsx
// ButtonProps
export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
  ref?: Ref<HTMLButtonElement>;
  // ...
};

// Button.tsx
export const Button = ({ ref, children, ...props }: ButtonProps) => (
  <button ref={ref} {...props}>
    {children}
  </button>
);
```

## 9. Props API

Props define the public contract. Changing existing props implies a major version impact for the component library.

### Runtime Rules

- If the props object is passed whole, it MUST be named `props`.
- If props are used by component logic, destructure them in the function arguments.
- If a component only proxies props to another component or root element, pass `props` whole.
- Avoid deep destructuring in function arguments; destructure nested data inside the component body.
- Collect rest props last as `restProps`.
- Spread `restProps` last on the root element so consumer attributes can override internal defaults when needed.
- If a prop name conflicts with a local variable, rename it during destructuring with the `Prop` suffix, e.g. `value: valueProp`.
- Group third-party configuration or element parameter sets into objects with the `Props` suffix, e.g. `floatingUiProps`. If many element prop groups appear, consider compound components instead.
- Defaults SHOULD be assigned during destructuring, except props that participate in internal or context state. State and context props must remain `undefined` until priority is computed.
- `className` MUST apply to the block/root element and be appended last in `clsx`.
- Element class props MUST be named `{elementName}ClassName` and appended last in that element's `clsx`.
- `testId` MUST map to `data-testid` on the block/root element.
- Element test ids MUST be named `{elementName}TestId` and map to `data-testid` on that element.
- Native event handlers MUST be named `on{Event}`, receive only the React event object, and external handlers from props MUST be called when internal logic wraps them.
- Non-native event handlers MUST be named `on{Entity}{Event}`, receive processed component data only, never receive a native event object, and SHOULD take one object argument.
- In prop objects and other data structures, properties SHOULD be sorted alphabetically. Event handlers SHOULD be grouped at the end.

### Typing Rules

- Props types MUST be named `{ComponentName}Props`.
- Props types MUST extend native attributes of the root element.
- `className` and `testId` MUST be optional strings. Do not redeclare `className` when native attributes already provide it.
- Native event handler types MUST use React event types for the relevant element.
- Non-native event handler types MUST use the component's internal data types.
- Generic component handlers MUST use the generic parameter for their data.

## 10. TypeScript Generics

- A component MUST be generic if it operates on data whose type is determined by calling code.
- Generic types MUST infer automatically from props. Consumers SHOULD NOT need explicit `<T>`.
- In `.tsx`, generic component syntax MUST use a trailing comma: `<T,>`.
- Generic components MUST pass the generic parameter through props, context, handlers, refs, and the `@utils/memo` helper.

## 11. Hierarchical CSS Model

CSS Modules classes mirror the Block-Modifier-Element model.

- Use class selectors only. IDs, tag selectors, and non-camelCase selectors are forbidden for component styles.
- All selector names MUST be English, Latin-only, camelCase after their prefix.
- **Block**: root class. Name matches the component name in camelCase, e.g. `.radioButton`. Blocks MAY be nested in other blocks.
- **Modifier**: class that changes block appearance, state, or behavior. Name starts with `_`, e.g. `._disabled`, `._sizeL`, `._variantPrimary`. Modifiers apply to the block only. Boolean modifiers MUST use single-key names (`_disabled`); non-boolean modifiers MUST use key-value pair names (`_sizeL`, `_variantPrimary`).
- **Element**: internal helper DOM/UI node. Name starts with `__`, e.g. `.__icon`, `.__label`. Elements MUST NOT exist outside their block, MUST NOT be nested inside other elements, and MUST NOT have modifiers.

## 12. CSS Specificity and Composition

- Base selectors for blocks, modifiers, and elements SHOULD have specificity `0-1-0`.
- Specificity `0-1-1` is allowed for pseudo-classes, pseudo-elements, and attributes.
- Selector nesting is allowed only for modifier combinations or modifier influence on an element.
- Specificity `0-3-0` is the maximum. If more classes are needed, refactor state logic or introduce an aggregate modifier.
- Do not combine two element classes to style element relationships.
- `!important` is forbidden.
- `:hover` selectors MUST be wrapped in `@media (any-hover: hover)`.
- Blocks MUST NOT have external `margin`. Elements MAY have margin only when it expresses internal UI layout.
- Use `clsx` for class composition; do not manually concatenate class strings.

## 13. Component CSS Tokens

- Components MUST expose style customization through component-level CSS variables.
- Token names MUST be flat and follow:

```text
--rig-[block]-[modifier]-[element]-[property]
```

- `block` and `property` are required. `modifier` and `element` are optional.
- All token name parts use camelCase and are separated with a single hyphen.
- Token values SHOULD reference semantic design-system tokens.
- All component tokens MUST be declared in the block class so the style API is discoverable and overrideable in one place.

## 14. CSS Grid Templating

- Use CSS Grid for layout templating instead of parsing children to force placement.
- The parent component SHOULD define the grid structure. Child components MAY declare their own position in that grid.
- With CSS Modules, named grid areas can be transformed by hashing. Prefer grid line numbers or pass area names through CSS variables when stable cross-file names are required.
- Grid styling MUST be opt-in:
  - parent grid components expose `isTemplatedContainer` and use `._isTemplatedContainer`;
  - child grid items expose `isTemplatedItem` and use `._isTemplatedItem`.
- Parent components MAY distribute templating state through context.

## Quick Checklist

When creating, changing, or reviewing a component, verify:

- [ ] One component type per directory; files separated by concern.
- [ ] Component, props, context value, and ref names follow required templates.
- [ ] Props extend native root attributes and preserve native behavior.
- [ ] Compound groups use children/context instead of parent-owned child configuration.
- [ ] State uses `useControlled`; priority is prop, context, default.
- [ ] Context is external, memoized, consumed through a hook, and generic-safe when needed.
- [ ] Polymorphic API uses `component`, `Component`, restricted tag types, dynamic props, and dynamic refs.
- [ ] Generic types infer from props; if memoized, use `@utils/memo`. Refs are passed as a `ref` prop (no `forwardRef` wrapping).
- [ ] Refs reach the root element; internal element refs use `{elementName}Ref`.
- [ ] Context providers use the React 19 short syntax `<XxxContext value={...}>` and consumers use `use(...)` (not `useContext`).
- [ ] Props destructuring, `restProps`, `className`, `testId`, defaults, and handlers follow the props rules.
- [ ] CSS classes use block `.component`, modifiers `._modifier`, and elements `.__element`.
- [ ] CSS specificity stays predictable; no `!important`; hover is media-gated; block has no margin.
- [ ] Component CSS variables follow `--rig-[block]-[modifier]-[element]-[property]` and are declared on the block.
- [ ] CSS Grid templating is opt-in through `isTemplatedContainer` / `isTemplatedItem`, not child parsing.
- [ ] `index.ts` exports the public API.
