Component Catalog
Full API reference for all src/ui/ primitives.
Import pattern: @/ui/{name}
Table of Contents
- Button
- IconButton
- TextField
- Heading
- Text
- Tag
- Banner
- Select
- Dialog
- Switch
- Checkbox
- Tooltip
- Other Components
- Icons
Button
Import
import { Button } from '@/ui/button';
Usage Example
<Button appearance="white" size="2" state="loading" iconLeft={<IconPlus />} shortcut="⌘S"> Save </Button>
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
appearance | 'white' | 'gray' | 'fade-gray' | 'fade' | 'fade-red' | 'red' | 'white' | Controls visual style |
size | '1' | '2' | '2' | Controls dimensions |
state | 'normal' | 'disabled' | 'loading' | — | Controls interactive state |
iconLeft | ReactElement | — | Icon rendered left of label |
iconRight | ReactElement | — | Icon rendered right of label |
shortcut | string | [string, string] | — | Keyboard shortcut badge |
asChild | boolean | false | Merges props onto child element via Radix Slot |
IconButton
Import
import { IconButton } from '@/ui/icon-button';
Usage Example
<IconButton appearance="fade" size="1" aria-label="Close"> <IconClose /> </IconButton>
Rules
- All variants are identical to
Button. aria-labelis required on everyIconButtoninstance — no exceptions.
TextField
Import
import { TextField } from '@/ui/text-field/text-field';
Usage Example
<TextField.Root> <TextField.Slot><IconSearch /></TextField.Slot> <TextField.Input placeholder="Search..." size="2" /> <TextField.Slot> <TextField.Error message="Required" id="field-error" /> </TextField.Slot> </TextField.Root>
Structure Rules
- Always wrap in
TextField.Root. No exceptions. - Only
TextField.SlotandTextField.Inputare valid direct children ofTextField.Root. - Maximum one
TextField.SlotbeforeTextField.Input. - Maximum one
TextField.SlotafterTextField.Input. - Slots auto-adjust input padding via
ResizeObserver.
TextField.Input Props
| Prop | Type | Values |
|---|---|---|
size | string | '1' | '2' | '3' |
appearance | string | 'gray' | 'public' |
state | string | 'normal' | 'disabled' | 'read-only' | 'invalid' |
error | string | Error message string |
Heading
Import
import { Heading } from '@/ui/heading';
Usage Example
<Heading as="h2" size="5" color="white" weight="semibold"> Title </Heading>
Props
| Prop | Type | Values |
|---|---|---|
as | string | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' |
size | string | '1' – '8' |
color | string | 'white' | 'gray' |
weight | string | 'medium' | 'semibold' | 'bold' |
Notes
- Sizes
7and8usefont-display(ABC Favorit). - Use the
Headingcomponent with thesizeprop — do not use raw Tailwind text classes.
Text
Import
import { Text } from '@/ui/text';
Usage Example
<Text as="p" size="2" color="gray"> Description </Text>
Props
| Prop | Type | Values |
|---|---|---|
as | string | 'span' | 'p' | 'strong' |
size | string | '1' – '9' |
color | string | 'white' | 'gray' | 'red' | 'yellow' |
weight | string | 'normal' | 'medium' | 'semibold' | 'bold' |
Notes
- Use the
Textcomponent with thesizeprop — do not use raw Tailwind text classes.
Tag
Import
import { Tag } from '@/ui/tag';
Usage Example
<Tag appearance="green" variant="solid" size="1"> Active </Tag>
Props
| Prop | Type | Values |
|---|---|---|
appearance | string | 'gray' | 'dimgray' | 'green' | 'red' | 'yellow' | 'blue' | 'orange' | 'violet' | 'sand' |
variant | string | 'solid' | 'outline' |
size | string | '1' | '2' |
Banner
Import
import { Banner } from '@/ui/banner';
Usage Example
<Banner appearance="yellow" size="2"> Warning message </Banner>
Props
| Prop | Type | Values |
|---|---|---|
appearance | string | 'gray' | 'green' | 'red' | 'yellow' | 'blue' |
size | string | '1' | '2' |
Auto Icon Behavior
appearance value | Icon rendered |
|---|---|
'blue', 'gray' | Info |
'green' | Confetti |
'red', 'yellow' | Warning |
Select
Import
import * as Select from '@/ui/select';
Usage Example
<Select.Root value={val} onValueChange={setVal}> <Select.Trigger size="2" appearance="gray" /> <Select.Content> <Select.Label>Group</Select.Label> <Select.Item value="a">Option A</Select.Item> <Select.Separator /> <Select.Item value="b">Option B</Select.Item> </Select.Content> </Select.Root>
Select.Trigger Props
| Prop | Type | Values |
|---|---|---|
size | string | '1' | '2' |
appearance | string | 'gray' | 'ghost' |
state | string | 'normal' | 'invalid' |
Dialog
Import
import * as Dialog from '@/ui/dialog';
Usage Example
<Dialog.Root> <Dialog.Trigger asChild> <Button>Open</Button> </Dialog.Trigger> <Dialog.Content size="1"> <Dialog.Title>Confirm</Dialog.Title> <p>Are you sure?</p> </Dialog.Content> </Dialog.Root>
Dialog.Content size Values
| Value | Max Width / Dimensions |
|---|---|
'1' | max-w-lg |
'2' | 1200px |
'full-screen' | 80vw / 80vh |
Notes
includeCloseButtondefaults totrue.- Dialog auto-manages focus trap and escape key handling.
Switch
Import
import { Switch } from '@/ui/switch';
Usage Example
<Switch checked={on} onCheckedChange={setOn} disabled={false} />
Props
| Prop | Type |
|---|---|
checked | boolean |
onCheckedChange | (checked: boolean) => void |
disabled | boolean |
Checkbox
Import
import { Checkbox } from '@/ui/checkbox';
Usage Example
<Checkbox checked={val} onCheckedChange={setVal} />
Props
| Prop | Type | Notes |
|---|---|---|
checked | boolean | 'indeterminate' | 'indeterminate' is a supported value |
onCheckedChange | (checked: boolean | 'indeterminate') => void | — |
Tooltip
Import
import * as Tooltip from '@/ui/tooltip';
Usage Example
<Tooltip.Root> <Tooltip.Trigger asChild> <Button>Hover</Button> </Tooltip.Trigger> <Tooltip.Content>Tip text</Tooltip.Content> </Tooltip.Root>
Other Components
| Component | Import Path | Notes |
|---|---|---|
Avatar | @/ui/avatar | Compound: Root, Image, Fallback. variant: 'rounded' | 'squared' |
Tabs | @/ui/tabs | Namespace: Root, List, Trigger, Content |
Kbd | @/ui/kbd | appearance: 'gray' | 'inverted' | 'red' | 'fade' | 'fade-gray' |
DropdownMenu | @/ui/dropdown-menu | Namespace: Root, Trigger, Content, Item, Separator |
Drawer | @/ui/drawer | Side drawer overlay |
Popover | @/ui/popover | Popover overlay |
ContextMenu | @/ui/context-menu | Right-click menu |
Skeleton | @/ui/skeleton | Loading placeholder |
LoadingDots | @/ui/loading-dots | Animated loader |
CopyButton | @/ui/copy-button | One-click copy |
EmptyState | @/ui/empty-state | Empty state placeholder |
Card | @/ui/card | Card container |
Pagination | @/ui/pagination | Page navigation |
Breadcrumb | @/ui/breadcrumb | Breadcrumb trail |
Link | @/ui/link | Styled anchor link |
InternalLink | @/ui/internal-link | App-internal navigation link |
Calendar | @/ui/calendar | Date picker |
Collapsible | @/ui/collapsible | Expandable section |
ScrollArea | @/ui/scroll-area | Custom scrollbar container |
BulkActions | @/ui/bulk-actions | Compound: Root, BottomBar, CheckBoxItem, SelectAll |
ToggleGroup | @/ui/toggle-group | Radio / multi-select group |
SensitiveField | @/ui/sensitive-field | Masked sensitive data input |
Icons
- Location:
@/ui/icons/icon-{name}.tsx - Count: 100+ icons available.
Commonly Used Icons
| Icon | Icon | Icon |
|---|---|---|
IconClose | IconCheck | IconCheckmark |
IconSearch | IconPlus | IconMinus |
IconTrash | IconEdit | IconCopy |
IconChevronDown | IconChevronUp | IconChevronLeft |
IconChevronRight | IconInformation | IconWarning |
IconConfetti | IconExternalLink | IconSettings |
Live Examples
Located at: src/app/(internal)/design/components/{name}/page.tsx
Design Tokens
Defined in src/styles/globals.css using Tailwind CSS v4 with CSS custom properties.
Color System
Built on Radix UI Colors. Each color has 12 steps plus alpha variants.
Gray Scale
Primary neutral scale. Used for text, borders, and backgrounds.
| Variant | Values |
|---|---|
| Full opacity | gray-1 – gray-10 |
| Semi-transparent alpha | gray-a1 – gray-a10 |
| Forced-light context | light-gray-* |
Semantic Colors
| Color | Primary Usage | Example Classes |
|---|---|---|
violet | Brand accent | bg-violet-3, text-violet-11, border-violet-4 |
green | Success, active states | bg-green-3, text-green-11 |
red | Error, destructive actions | bg-red-3, text-red-11 |
yellow | Warnings | bg-yellow-3, text-yellow-11 |
blue | Informational | bg-blue-3, text-blue-11 |
orange | Caution | bg-orange-3, text-orange-11 |
sand | Neutral warm | bg-sand-3, text-sand-11 |
cyan | Secondary accent | bg-cyan-3, text-cyan-11 |
Color Step Convention
| Step Range | Usage |
|---|---|
1 – 2 | Subtle backgrounds |
3 – 4 | UI element backgrounds, borders |
5 – 6 | Hovered and active states |
7 – 8 | Solid backgrounds |
9 – 10 | High-contrast text, solid fills |
11 – 12 | Maximum contrast text |
Background Values
| Mode | Value |
|---|---|
| Light | #fdfdfd |
| Dark | #000 |
Typography
| Token | Font Family | Usage |
|---|---|---|
font-sans | Inter | Body text, UI (default) |
font-display | ABC Favorit | Large headings (Heading size 7–8) |
font-domaine | Domaine | Serif accents |
font-mono | Commit Mono | Code, monospace content |
Rule: Use Heading and Text components with the size prop. Do not use raw Tailwind text size classes directly.
Sizing Scale
| Size | Height | Padding | Text Size | Border Radius |
|---|---|---|---|---|
'1' | h-6 (24px) | px-2 | text-xs | rounded-lg |
'2' | h-8 (32px) | px-3 | text-sm | rounded-xl |
'3' | h-10 (40px) | px-3 | text-sm | rounded-xl |
Border Radius Reference
| Class | Value | Usage |
|---|---|---|
rounded-lg | 0.5rem | Size 1 components |
rounded-xl | 0.75rem | Size 2–3 components |
rounded-2xl | 1rem | Banners, larger elements |
rounded-3xl | 1.5rem | Cards |
rounded-4xl | 2rem | Dialogs |
Shadows
| Token | Usage |
|---|---|
--shadow-3xl | Large shadow |
--shadow-4xl | Extra-large shadow |
--shadow-button | Button-specific shadow |
Animations
Scale & Fade
| Class | Behavior |
|---|---|
animate-open-scale-in-fade | Open: scale in + fade |
animate-open-scale-up-fade | Open: scale up + fade |
animate-close-scale-out-fade | Close: scale out + fade |
Slide & Fade
| Class | Behavior |
|---|---|
animate-open-slide-up-fade | Open: slide up + fade |
animate-open-slide-down-fade | Open: slide down + fade |
animate-close-slide-up-fade | Close: slide up + fade |
animate-close-slide-down-fade | Close: slide down + fade |
Utility Animations
| Class | Effect |
|---|---|
animate-shine | Shine sweep |
animate-disco | Disco effect |
animate-scroll-x | Horizontal scroll |
animate-caret-blink | Blinking caret |
animate-accordion-slide-down | Accordion open |
animate-accordion-slide-up | Accordion close |
animate-collapsible-slide-down | Collapsible open |
animate-collapsible-slide-up | Collapsible close |
animate-fade-in | Fade in |
animate-fade-out | Fade out |
Custom Utility Classes
| Class | Effect |
|---|---|
fade-in-black | Horizontal fade mask |
bg-shine | Animated shine background |
bg-gradient-fade | Gradient fade background (used in Banner) |
effect-font-styling | Display font styling for Heading sizes 7–8 |
Dark Mode
- Implemented via Tailwind
dark:prefix. - Background flips:
#fdfdfd→#000. Buttonwithappearance="white"inverts automatically.- Gray scales adjust automatically via Radix.
- Use
dark:only for explicit manual overrides — not as a default styling approach.
Component Patterns
CVA — Class Variance Authority
All variants use CVA for type-safe styling.
Pattern
import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/cn'; const myVariants = cva('base-classes', { variants: { appearance: { gray: 'bg-gray-a2 border-gray-a3 text-gray-9', white: 'bg-black text-white dark:bg-white dark:text-black', }, size: { '1': 'h-6 text-xs px-2 rounded-lg', '2': 'h-8 text-sm px-3 rounded-xl', }, }, defaultVariants: { appearance: 'gray', size: '2' }, }); interface MyProps extends React.ComponentProps<'div'>, VariantProps<typeof myVariants> {} function MyComponent({ appearance, size, className, ...props }: MyProps) { return <div className={cn(myVariants({ appearance, size, className }))} {...props} />; }
Conventions
| Rule | Detail |
|---|---|
| Size values | Always string literals: '1', '2', '3' — never raw numbers |
appearance | Controls visual style only |
size | Controls dimensions only |
state | Controls interactive state only |
className | Always pass through CVA for merge support |
| Merging | Always use cn() from @/lib/cn — wraps tailwind-merge + clsx |
Compound Component Pattern
Two patterns in use. Choose based on coupling level.
Object Namespace
Used for tightly coupled parts (TextField, Avatar, BulkActions).
export const TextField = { Root, Slot, Input, Error }; <TextField.Root> <TextField.Slot>...</TextField.Slot> <TextField.Input size="2" /> </TextField.Root>
Named Exports
Used for Radix-based primitives (Select, Dialog, Tooltip, DropdownMenu).
import * as Select from '@/ui/select'; <Select.Root> <Select.Trigger /> <Select.Content> <Select.Item value="x">X</Select.Item> </Select.Content> </Select.Root>
Slot Pattern (asChild)
Uses @radix-ui/react-slot to merge props onto a child element instead of rendering a wrapper.
<Button asChild> <Link href="/settings">Settings</Link> </Button> <Dialog.Trigger asChild> <IconButton appearance="fade"><IconSettings /></IconButton> </Dialog.Trigger>
TextField Slot System
Slots auto-adjust input padding via ResizeObserver.
<TextField.Root> <TextField.Slot><IconSearch /></TextField.Slot> <TextField.Input placeholder="Search..." /> <TextField.Slot><Button size="1" appearance="fade">Clear</Button></TextField.Slot> </TextField.Root>
Hard Rules
- Maximum one
TextField.SlotbeforeTextField.Input. - Maximum one
TextField.SlotafterTextField.Input. - Only
TextField.SlotandTextField.Inputare valid direct children ofTextField.Root.
State Management
Use the state prop — not individual boolean props.
<Button state="loading">Save</Button> <TextField.Input state="disabled" /> <TextField.Input state="read-only" />
Shared Styling
src/ui/shared.ts exports constants for consistent dropdown and floating bar styling.
import { dropdown, floatingBottomBar } from '@/ui/shared'; <div className={cn(dropdown.content.appearance, dropdown.content.sizing)}> <div className={cn(dropdown.item.sizing, dropdown.item.appearance.gray)}> Item </div> </div>
Server vs Client Components
| Rule | Detail |
|---|---|
| Default | Server Components |
'use client' | Add only at the lowest interactive leaf — not at higher levels |
Pre-classified Components
| Type | Components |
|---|---|
| Client (already marked) | TextField, Checkbox, Dialog, Drawer, Collapsible, Calendar, BulkActions |
| Server-safe | Button, Heading, Text, Tag, Banner, Card, EmptyState, Kbd |
Accessibility
| Component / Pattern | Requirement |
|---|---|
IconButton | aria-label is required — always |
Dialog | Auto-manages focus trap and escape key |
Banner | Uses role="alert" automatically |
TextField.Error | Wires aria-describedby automatically |
Checkbox | 'indeterminate' is a supported checked value |