Comprehensive reference for the DSAI component library. Guides AI agents on installing, importing, composing, and extending DSAI components in consumer projects. Covers the full component catalog (38+), type system, FSM patterns, compound components, hooks, utilities, and critical conventions.
From hugin-v0npx claudepluginhub michelve/hugin-marketplace --plugin hugin-v0This skill uses the workspace's default tool permissions.
references/component-catalog.mdreferences/compound-patterns.mdreferences/fsm-patterns.mdreferences/registry-usage.mdGuides browser automation with Playwright, Puppeteer, Selenium for e2e testing and scraping. Teaches reliable selectors, auto-waits, isolation to fix flaky tests.
Provides checklists to review code for functionality, quality, security, performance, tests, and maintainability. Use for PRs, audits, team standards, and developer training.
Enforces A/B test setup with gates for hypothesis locking, metrics definition, sample size calculation, assumptions checks, and execution readiness before implementation.
Components are installed via the dsai CLI — not npm/yarn/pnpm install.
dsai add button # Single component
dsai add button modal card # Multiple components
dsai add --all # All components
dsai add --type hook # All hooks
This copies source files into the consumer project at src/client/components/ui/<name>/. Components are local source code that you own and can modify.
The registry resolves dependencies via BFS + topological sort, transforms relative imports to path aliases (@/), and writes to configured alias directories (components/ui/, hooks/, utils/). Existing files are never overwritten unless the --overwrite flag is passed.
After installing a component with dsai add, import it, render it, and customize:
// 1. Import the component from your local source
import { Button } from '@/components/ui/button';
// 2. Render it
function App() {
return (
<Button variant="primary" size="md" onClick={() => alert('Clicked!')}>
Get Started
</Button>
);
}
// 3. Customize with DSAI tokens (see dsai-styling skill)
// Components use Bootstrap 5 classes + --dsai-* CSS custom properties
// Use cn() for conditional classes — NOT tailwind-merge or clsx
Components — import from @/components/ui/<name>:
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { Modal } from '@/components/ui/modal';
Type imports — import from the .types file:
import type { ButtonProps } from '@/components/ui/button/Button.types';
import type { CardProps } from '@/components/ui/card/Card.types';
import type { ModalProps } from '@/components/ui/modal/Modal.types';
Hooks — import from @/hooks/:
import { useControllableState } from '@/hooks/useControllableState';
import { useFocusTrap } from '@/hooks/useFocusTrap';
import { useDarkMode } from '@/hooks/useDarkMode';
Utilities — import from @/utils/ or @/lib/:
import { cn } from '@/utils/cn';
import { sanitizeHtml } from '@/utils/sanitize';
Note:
@dsai-io/reactis the internal monorepo package. Consumer projects import from@/components/ui/<name>— never from@dsai-io/react.
Styling: Components use Bootstrap 5 classes and
--dsai-*CSS custom properties. See the dsai-styling skill for token usage,cn()patterns, and dark mode.
Every component follows forwardRef with an explicit displayName:
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
function Button(props, ref) {
/* ... */
}
);
Button.displayName = 'Button';
For performance-optimized components: memo(forwardRef(function Name(...))) + displayName.
Each component directory contains:
| File | Purpose |
|---|---|
ComponentName.tsx | Main component with forwardRef |
ComponentName.types.ts | TypeScript prop interfaces (always separate) |
ComponentName.fsm.ts | Finite State Machine (interactive components) |
ComponentName.test.tsx | Unit tests (Jest 30 + React Testing Library) |
ComponentName.a11y.test.tsx | Accessibility tests (jest-axe) |
index.ts | Barrel exports |
Collapsible sections.
Navigation breadcrumbs.
Sub-components: Card.Header, Card.Body, Card.Footer, Card.Image, Card.Title, Card.Subtitle, Card.Text, Card.Link, Card.ImgOverlay
| Prop | Type | Notes |
|---|---|---|
variant | 'elevated' | 'outlined' | 'ghost' | Visual style |
color | SemanticColorVariant | Semantic color |
size | 'sm' | 'md' | 'lg' | Component size |
horizontal | boolean | Horizontal layout |
interactive | boolean | Hover/active states |
href | string | Makes card a link |
<Card variant="elevated" color="primary">
<Card.Header>Title</Card.Header>
<Card.Body>Content</Card.Body>
<Card.Footer>Actions</Card.Footer>
</Card>
List of cards.
Sub-components: CarouselItem, CarouselControl, CarouselIndicators, CarouselCaption, CarouselPauseButton
Layout container.
Sub-components: Modal.Header, Modal.Body, Modal.Footer, Modal.Title, Modal.Description
Portal rendering, focus trap, scroll lock, FSM-driven animations.
| Prop | Type | Notes |
|---|---|---|
isOpen | boolean | Controlled open state |
onClose | () => void | Close callback |
size | 'sm' | 'md' | 'lg' | 'xl' | 'fullscreen' | Modal size |
centered | boolean | Vertically centered |
backdrop | boolean | 'static' | Backdrop behavior |
closeOnEscape | boolean | ESC key closes modal |
initialFocusRef | RefObject | Focus target on open |
<Modal isOpen={open} onClose={close}>
<Modal.Header closeButton>Edit Profile</Modal.Header>
<Modal.Body>Form content</Modal.Body>
<Modal.Footer>
<Button onClick={close}>Save</Button>
</Modal.Footer>
</Modal>
Navigation bar.
Side panel overlay.
Sub-components: Tab, TabList, TabPanel
| Prop | Type | Notes |
|---|---|---|
variant | 'underline' | 'pills' | 'tabs' | Visual style |
orientation | 'horizontal' | 'vertical' | Tab direction |
activationMode | 'automatic' | 'manual' | Focus vs click activation |
lazyMount | boolean | Lazy render panels |
unmountOnExit | boolean | Unmount inactive panels |
<Tabs variant="pills" orientation="horizontal">
<TabList aria-label="Settings">
<Tab id="general">General</Tab>
<Tab id="security">Security</Tab>
</TabList>
<TabPanel id="general">General settings</TabPanel>
<TabPanel id="security">Security settings</TabPanel>
</Tabs>
Enhanced tabs with closable and addable tabs.
FSM-based interactive component.
| Prop | Type | Notes |
|---|---|---|
variant | SemanticColorVariant | 'outline-*' | 'subtle-*' | 'ghost' | 'link' | Visual style |
size | 'sm' | 'md' | 'lg' | 'icon' | Button size |
loading | boolean | Shows spinner, disables interaction |
error | boolean | Error visual state |
disabled | boolean | Disabled state |
startIcon | ReactNode | Icon before label |
endIcon | ReactNode | Icon after label |
announceText | string | Screen reader announcement |
as | ElementType | Polymorphic — renders as button, a, or custom |
FSM states: idle → hovered → focused → pressed → disabled → loading → error
FSM events: HOVER, BLUR, FOCUS, PRESS, RELEASE, DISABLE, ENABLE, START_LOADING, STOP_LOADING, ERROR, CLEAR_ERROR
Check controls.
Text input with built-in validation.
| Prop | Type | Notes |
|---|---|---|
type | 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search' | Input type |
size | 'sm' | 'md' | 'lg' | Input size |
label | string | Associated label |
helperText | string | Help text below input |
error | string | boolean | Error message or state |
success | string | boolean | Success state |
prefix | ReactNode | Left addon |
suffix | ReactNode | Right addon |
clearable | boolean | Show clear button |
showCount | boolean | Character count |
floating | boolean | Floating label |
plaintext | boolean | Read-only plain text |
Radio controls.
Generic typed dropdown select.
| Prop | Type | Notes |
|---|---|---|
SelectProps<T> | Generic | Typed to option shape |
multiple | boolean | Multi-select |
searchable | boolean | Filterable options |
clearable | boolean | Clear selection |
loading | boolean | Loading state |
renderOption | (option: T) => ReactNode | Custom option rendering |
renderValue | (value: T) => ReactNode | Custom value rendering |
Toggle switch.
Searchable select variant.
Card that acts as a selection control.
Notification banners.
User avatars.
Status indicators.
Large display text.
h1–h6 elements.
Paragraph text.
Combined typography component.
List displays.
Page navigation.
Sub-components: PopoverHeader, PopoverBody, PopoverCloseButton
Progress bars.
Scroll-based navigation highlighting.
Loading indicators.
Data tables.
Toast notifications.
Tooltips.
Dropdown menus.
Defined in types/primitives.ts:
type SemanticColorVariant = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'light' | 'dark';
type ComponentSize = 'sm' | 'md' | 'lg';
type ExtendedSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'xxl';
type FeedbackVariant = 'success' | 'error' | 'warning' | 'info' | 'default';
type Alignment = 'start' | 'center' | 'end';
type Orientation = 'horizontal' | 'vertical';
type PolymorphicComponentProps<C extends React.ElementType, Props> =
Props &
Omit<React.ComponentPropsWithRef<C>, keyof Props> &
{ as?: C };
Allowed: className, style, id, title, tabIndex, data-testid, role, and ARIA attributes.
Blocked: onClick, onMouseOver, onLoad, dangerouslySetInnerHTML, and all event handlers.
Components define their own safe event handler props explicitly. Never spread unknown props.
interface FSMStateBase { status?: string; }
interface FSMEventBase { type: string; }
type FSMReducer<State, Event> = (state: State, event: Event) => State;
interface VisualStateBase { state: string; shouldRender: boolean; modifiers?: string[]; }
Interactive components use useReducer with a finite state machine:
const [fsmState, dispatch] = useReducer(
buttonFSMReducer,
initialProps,
createInitialButtonFSMState
);
Events dispatched: HOVER, BLUR, FOCUS, PRESS, RELEASE, DISABLE, ENABLE, START_LOADING, STOP_LOADING, ERROR, CLEAR_ERROR
Parent and children communicate via React Context:
<Card variant="elevated" color="primary">
<Card.Header>Title</Card.Header>
<Card.Body>Content</Card.Body>
<Card.Footer>Actions</Card.Footer>
</Card>
Components support both modes via the useControllableState hook:
// Controlled
<Select value={selected} onChange={setSelected} options={options} />
// Uncontrolled
<Select defaultValue="option1" options={options} />
Simple class name composition (NOT tailwind-merge). Uses Bootstrap 5 class naming:
cn('btn', variant && `btn-${variant}`, size === 'lg' && 'btn-lg', className)
// Filters falsy values, joins with space
useFocusTrap — Trap focus within a containeruseRovingFocus — Arrow-key focus navigationuseReducedMotion — Detect prefers-reduced-motionuseMediaQuery — Match media queriesuseIsMobile / useIsTablet / useIsDesktop / useIsLargeDesktop — Breakpoint hooksuseScrollLock — Prevent body scrolluseDarkMode — Dark mode stateuseControllableState — Controlled/uncontrolled stateusePrevious — Previous valueuseLocalStorage / useSessionStorage — Persisted stateuseDebounce / useThrottle — TiminguseField — Field-level state and validationuseForm — Form state managementuseClickOutside — Detect clicks outside elementuseKeyPress — Key event handlinguseHover — Hover detectionuseIntersectionObserver — Visibility detectionuseResizeObserver — Size change detectionuseAsync — Async operation stateuseCallbackRef — Stable callback referenceuseId — Unique ID generationuseMounted — Mount state tracking| Category | Functions |
|---|---|
| Core | cn(), generateId, clamp, isBrowser, mergeRefs, prefersReducedMotion |
| Accessibility | announceToScreenReader, focusableSelectors, trapFocus, getAnimationDuration, shouldAnimate |
| Keyboard | isEnterKey, isEscapeKey |
| DOM | mergeRefs |
| Objects | deepMerge, omit, pick |
| String | capitalize, slugify, truncate, getVariantClass |
| Timing | debounce, throttle |
| Safety | sanitizeHtml, sanitizeUrl, generateCryptoId, copyToClipboard |
| Validation | isValidEmail, isValidHref, isSafeHref, isValidUrl, isExternalUrl |
| Color | getContrastRatio, hexToRgb, rgbToHsl, hslToHex, meetsWCAG |
| Forms | validateField, validateForm, parseFormData, serializeForm |
| Misc | mapPlacement, normalizeTriggers, getSafeInputProps, ClearIcon |
forwardRef + displayName — not optional.*.types.ts files.SemanticColorVariant for colors — never hardcode hex/rgb values.ComponentSize for sizing (sm | md | lg).useReducer) for state management.SafeHTMLAttributes — never pass event handlers through prop spread; define them explicitly.cn() for class composition — Bootstrap 5 classes, not Tailwind.jest-axe (a11y) and React Testing Library.@/ not @dsai-io/react or any npm package.