SKILL.md

Component Catalog

Full API reference for all src/ui/ primitives.
Import pattern: @/ui/{name}


Table of Contents

  1. Button
  2. IconButton
  3. TextField
  4. Heading
  5. Text
  6. Tag
  7. Banner
  8. Select
  9. Dialog
  10. Switch
  11. Checkbox
  12. Tooltip
  13. Other Components
  14. Icons

Button

Import

import { Button } from '@/ui/button';

Usage Example

<Button appearance="white" size="2" state="loading" iconLeft={<IconPlus />} shortcut="⌘S">
  Save
</Button>

Props

PropTypeDefaultNotes
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
iconLeftReactElementIcon rendered left of label
iconRightReactElementIcon rendered right of label
shortcutstring | [string, string]Keyboard shortcut badge
asChildbooleanfalseMerges 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-label is required on every IconButton instance — 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.Slot and TextField.Input are valid direct children of TextField.Root.
  • Maximum one TextField.Slot before TextField.Input.
  • Maximum one TextField.Slot after TextField.Input.
  • Slots auto-adjust input padding via ResizeObserver.

TextField.Input Props

PropTypeValues
sizestring'1' | '2' | '3'
appearancestring'gray' | 'public'
statestring'normal' | 'disabled' | 'read-only' | 'invalid'
errorstringError message string

Heading

Import

import { Heading } from '@/ui/heading';

Usage Example

<Heading as="h2" size="5" color="white" weight="semibold">
  Title
</Heading>

Props

PropTypeValues
asstring'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
sizestring'1''8'
colorstring'white' | 'gray'
weightstring'medium' | 'semibold' | 'bold'

Notes

  • Sizes 7 and 8 use font-display (ABC Favorit).
  • Use the Heading component with the size prop — 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

PropTypeValues
asstring'span' | 'p' | 'strong'
sizestring'1''9'
colorstring'white' | 'gray' | 'red' | 'yellow'
weightstring'normal' | 'medium' | 'semibold' | 'bold'

Notes

  • Use the Text component with the size prop — 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

PropTypeValues
appearancestring'gray' | 'dimgray' | 'green' | 'red' | 'yellow' | 'blue' | 'orange' | 'violet' | 'sand'
variantstring'solid' | 'outline'
sizestring'1' | '2'

Banner

Import

import { Banner } from '@/ui/banner';

Usage Example

<Banner appearance="yellow" size="2">
  Warning message
</Banner>

Props

PropTypeValues
appearancestring'gray' | 'green' | 'red' | 'yellow' | 'blue'
sizestring'1' | '2'

Auto Icon Behavior

appearance valueIcon 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

PropTypeValues
sizestring'1' | '2'
appearancestring'gray' | 'ghost'
statestring'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

ValueMax Width / Dimensions
'1'max-w-lg
'2'1200px
'full-screen'80vw / 80vh

Notes

  • includeCloseButton defaults to true.
  • 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

PropType
checkedboolean
onCheckedChange(checked: boolean) => void
disabledboolean

Checkbox

Import

import { Checkbox } from '@/ui/checkbox';

Usage Example

<Checkbox checked={val} onCheckedChange={setVal} />

Props

PropTypeNotes
checkedboolean | '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

ComponentImport PathNotes
Avatar@/ui/avatarCompound: Root, Image, Fallback. variant: 'rounded' | 'squared'
Tabs@/ui/tabsNamespace: Root, List, Trigger, Content
Kbd@/ui/kbdappearance: 'gray' | 'inverted' | 'red' | 'fade' | 'fade-gray'
DropdownMenu@/ui/dropdown-menuNamespace: Root, Trigger, Content, Item, Separator
Drawer@/ui/drawerSide drawer overlay
Popover@/ui/popoverPopover overlay
ContextMenu@/ui/context-menuRight-click menu
Skeleton@/ui/skeletonLoading placeholder
LoadingDots@/ui/loading-dotsAnimated loader
CopyButton@/ui/copy-buttonOne-click copy
EmptyState@/ui/empty-stateEmpty state placeholder
Card@/ui/cardCard container
Pagination@/ui/paginationPage navigation
Breadcrumb@/ui/breadcrumbBreadcrumb trail
Link@/ui/linkStyled anchor link
InternalLink@/ui/internal-linkApp-internal navigation link
Calendar@/ui/calendarDate picker
Collapsible@/ui/collapsibleExpandable section
ScrollArea@/ui/scroll-areaCustom scrollbar container
BulkActions@/ui/bulk-actionsCompound: Root, BottomBar, CheckBoxItem, SelectAll
ToggleGroup@/ui/toggle-groupRadio / multi-select group
SensitiveField@/ui/sensitive-fieldMasked sensitive data input

Icons

  • Location: @/ui/icons/icon-{name}.tsx
  • Count: 100+ icons available.

Commonly Used Icons

IconIconIcon
IconCloseIconCheckIconCheckmark
IconSearchIconPlusIconMinus
IconTrashIconEditIconCopy
IconChevronDownIconChevronUpIconChevronLeft
IconChevronRightIconInformationIconWarning
IconConfettiIconExternalLinkIconSettings

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.

VariantValues
Full opacitygray-1gray-10
Semi-transparent alphagray-a1gray-a10
Forced-light contextlight-gray-*

Semantic Colors

ColorPrimary UsageExample Classes
violetBrand accentbg-violet-3, text-violet-11, border-violet-4
greenSuccess, active statesbg-green-3, text-green-11
redError, destructive actionsbg-red-3, text-red-11
yellowWarningsbg-yellow-3, text-yellow-11
blueInformationalbg-blue-3, text-blue-11
orangeCautionbg-orange-3, text-orange-11
sandNeutral warmbg-sand-3, text-sand-11
cyanSecondary accentbg-cyan-3, text-cyan-11

Color Step Convention

Step RangeUsage
12Subtle backgrounds
34UI element backgrounds, borders
56Hovered and active states
78Solid backgrounds
910High-contrast text, solid fills
1112Maximum contrast text

Background Values

ModeValue
Light#fdfdfd
Dark#000

Typography

TokenFont FamilyUsage
font-sansInterBody text, UI (default)
font-displayABC FavoritLarge headings (Heading size 78)
font-domaineDomaineSerif accents
font-monoCommit MonoCode, monospace content

Rule: Use Heading and Text components with the size prop. Do not use raw Tailwind text size classes directly.


Sizing Scale

SizeHeightPaddingText SizeBorder Radius
'1'h-6 (24px)px-2text-xsrounded-lg
'2'h-8 (32px)px-3text-smrounded-xl
'3'h-10 (40px)px-3text-smrounded-xl

Border Radius Reference

ClassValueUsage
rounded-lg0.5remSize 1 components
rounded-xl0.75remSize 23 components
rounded-2xl1remBanners, larger elements
rounded-3xl1.5remCards
rounded-4xl2remDialogs

Shadows

TokenUsage
--shadow-3xlLarge shadow
--shadow-4xlExtra-large shadow
--shadow-buttonButton-specific shadow

Animations

Scale & Fade

ClassBehavior
animate-open-scale-in-fadeOpen: scale in + fade
animate-open-scale-up-fadeOpen: scale up + fade
animate-close-scale-out-fadeClose: scale out + fade

Slide & Fade

ClassBehavior
animate-open-slide-up-fadeOpen: slide up + fade
animate-open-slide-down-fadeOpen: slide down + fade
animate-close-slide-up-fadeClose: slide up + fade
animate-close-slide-down-fadeClose: slide down + fade

Utility Animations

ClassEffect
animate-shineShine sweep
animate-discoDisco effect
animate-scroll-xHorizontal scroll
animate-caret-blinkBlinking caret
animate-accordion-slide-downAccordion open
animate-accordion-slide-upAccordion close
animate-collapsible-slide-downCollapsible open
animate-collapsible-slide-upCollapsible close
animate-fade-inFade in
animate-fade-outFade out

Custom Utility Classes

ClassEffect
fade-in-blackHorizontal fade mask
bg-shineAnimated shine background
bg-gradient-fadeGradient fade background (used in Banner)
effect-font-stylingDisplay font styling for Heading sizes 78

Dark Mode

  • Implemented via Tailwind dark: prefix.
  • Background flips: #fdfdfd#000.
  • Button with appearance="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

RuleDetail
Size valuesAlways string literals: '1', '2', '3' — never raw numbers
appearanceControls visual style only
sizeControls dimensions only
stateControls interactive state only
classNameAlways pass through CVA for merge support
MergingAlways 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.Slot before TextField.Input.
  • Maximum one TextField.Slot after TextField.Input.
  • Only TextField.Slot and TextField.Input are valid direct children of TextField.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

RuleDetail
DefaultServer Components
'use client'Add only at the lowest interactive leaf — not at higher levels

Pre-classified Components

TypeComponents
Client (already marked)TextField, Checkbox, Dialog, Drawer, Collapsible, Calendar, BulkActions
Server-safeButton, Heading, Text, Tag, Banner, Card, EmptyState, Kbd

Accessibility

Component / PatternRequirement
IconButtonaria-label is required — always
DialogAuto-manages focus trap and escape key
BannerUses role="alert" automatically
TextField.ErrorWires aria-describedby automatically
Checkbox'indeterminate' is a supported checked value