# @k8ordo/ui Design Guide

How to build UI with the `@k8ordo/ui` design system.

## Setup

```bash
npm install @k8ordo/ui
```

```tsx
// 1. Load the stylesheet (once, at your entry point)
import '@k8ordo/ui/styles.css';

// 2. Wrap the app in the provider
import { UIProvider } from '@k8ordo/ui';

function App() {
  return (
    <UIProvider>
      <YourApp />
    </UIProvider>
  );
}

// 3. Use components
import { Button, Card } from '@k8ordo/ui';
```

### Choosing a stylesheet

There are two CSS entries. Pick the one that matches your project:

- **`styles.css` (default)** — prebuilt CSS. Use it if your project has no Tailwind (CSS Modules, plain CSS). A single import is all it takes, and no Tailwind setup is needed. Every library rule sits inside `@layer`, so your own unlayered CSS wins the cascade (the one exception is preflight's `[hidden] { display: none !important }`). Design tokens are readable as CSS custom properties on `:root` / `.dark` (`var(--fg-mute)`, …).
- **`tailwind.css`** — the Tailwind source entry. Import this in a Tailwind CSS 4 project and the design tokens become usable as Tailwind classes (`bg-bg-base`, …) in your own markup too. It contains `@import 'tailwindcss'` internally, so one line is enough for your project's CSS:

```css
@import '@k8ordo/ui/tailwind.css';
```

Note that either entry applies base styles document-wide. On top of Tailwind's
preflight (resets for headings, lists, and margins), the library's base layer
unsets bold on `b` / `strong` and italics on `i` / `em`, and sets `body`
typography defaults (`font-size`, `overflow-wrap: anywhere`,
`scrollbar-gutter: stable`, …). Adding it to an existing app restyles more than
the library's own components.

One more exception: `@k8ordo/ui/ai/response` (`Response`) needs streamdown's
classes to be generated by _your_ Tailwind build, so the prebuilt `styles.css`
alone will not render it completely. Using `Response` requires `tailwind.css`
plus a `@source` entry — see [ai-chat](references/ai-chat.md).

### Wording language (i18n)

The wording components own internally ("close", "required", "loading", …)
**defaults to Japanese**. It works without a provider and without passing
`messages`, so a Japanese app needs no setup at all.

To switch to English, pass `en` from `@k8ordo/ui/i18n`.

```tsx
import { UIProvider } from '@k8ordo/ui';
import { en } from '@k8ordo/ui/i18n';

<UIProvider messages={en}>
  <App />
</UIProvider>;
```

To replace only some of it, spread the dictionary and override those keys.

```tsx
<UIProvider messages={{ ...en, close: 'Dismiss' }}>
  <App />
</UIProvider>
```

Resolution order is **component prop > provider dictionary > built-in default
(Japanese)**. For a component with a wording prop of its own, such as
`Spinner`'s `label`, that prop wins over the dictionary.

> [Full key list and details](references/components.md) (the "i18n (message dictionary)" section)

## Design direction

### Core concept

**"Soft where you touch, precise where you read."**
(触れるものは柔らかく、読むものは端正に)

- **Space and shape lead**: the character of this design comes from generous spacing and soft shapes, not from color
- **Vary by the element's role**: things you touch (forms, buttons) are soft; things that inform (Alert, Badge) are precise
- **Let the spacing speak**: do not pack things in; leave room
- **Quiet motion**: restrained animation, subtle feedback
- **Calm color**: an OKLCH-based palette kept low-key through semantic tokens

Do not give everything the same tone. Soften in proportion to the element's role.

| Element                             | Direction                                            |
| ----------------------------------- | ---------------------------------------------------- |
| Forms (TextField, Textarea, Button) | Soft (large radii, pill buttons, generous padding)   |
| Cards and containers                | Soft (`rounded-xl`–`rounded-2xl`, gentle shadow)     |
| Alert, Badge                        | Precise (clarity of information comes first)         |
| Tabs, Breadcrumb                    | Sharp (elements that express structure stay precise) |

### Tone

Soft spacing and quiet refinement. The appeal is in space and shape.

## Aesthetic guidelines

### Page structure

**DO:**

- Use `bg-bg-subtle` (a light grey) for the page background
- Float content on white cards
- Leave enough room between cards (`gap-6`–`gap-10`)

**DON'T:**

- Lay flat cards on a pure white background
- Put everything in a Card
- Nest cards (Card in Card)

### Typography

**DO:**

- Use the Japanese fonts (Noto Sans JP, M PLUS 2)
- Keep to three font weights at most (`font-normal`, `font-medium`, `font-bold`)
- Lean on `font-medium` being 450 — lighter than the usual 500 — for delicate emphasis

**DON'T:**

- Use Inter / Roboto / Open Sans
- Use four or more font sizes on one screen
- Apply gradients to text

> [Typography details](references/typography.md)

### Color

**DO:**

- Follow the 60-30-10 rule (60% neutral, 30% supporting, 10% accent)
- Use semantic color tokens (`bg-bg-subtle`, `text-fg-mute`, …)
- Design dark mode as its own tone, not a derivative
- Keep color calm; let space and shape carry the design

**DON'T:**

- Use gradient backgrounds
- Use `bg-primary-bg` on hover — use `bg-bg-mute`
- Express state with opacity (`/90`) — use the dedicated token
- Spread vivid color over large areas
- Use raw palette colors (`bg-teal-500`) — use semantic tokens (`bg-primary-bg`)

> [Color details](references/color.md)

### Spacing

**DO:**

- Treat `p-8` as the standard padding (inside forms and cards)
- Express relatedness through the size of the gap (`mt-2` close, `mt-4` standard, `mt-8` between sections)
- Separate sections with Separator

**DON'T:**

- Put everything in a Card
- Nest cards (Card in Card)
- Use extremely tight spacing such as `gap-1`

> [Spacing details](references/spatial-design.md)

### Interaction

**DO:**

- Start from `transition-colors`
- Express focus with `focus-visible:ring-2 focus-visible:ring-border-info`
- Keep hover gentle with `hover:bg-bg-mute`

**DON'T:**

- Use bounce or spring easing
- Animate for longer than 300ms
- Use a strong primary color on hover

> [Interaction details](references/interaction-design.md)

## Component usage principles

### Button

Styling is unified through `color` and `variant`. Pill-shaped (`rounded-full`).

```tsx
import { Button } from '@k8ordo/ui';

// Primary action
<Button color="primary" variant="solid">Save</Button>

// Secondary action
<Button color="base" variant="outline">Cancel</Button>

// Text only
<Button variant="skeleton">View details</Button>

// Render as a link (renderItem prop)
<Button
  color="base"
  renderItem={({ className, children }) => (
    <a className={className} href="/settings">{children}</a>
  )}
>
  Settings
</Button>
```

### IconButton

Styling is controlled by `color` (not `variant`). `label` is required.

```tsx
import { IconButton } from '@k8ordo/ui';

<IconButton color="transparent" label="Copy"><CopyIcon /></IconButton>
<IconButton color="primary" label="Send"><SendIcon /></IconButton>

// Render as a link (renderItem prop)
<IconButton
  color="base"
  label="Home"
  renderItem={({ className, children, 'aria-label': ariaLabel, triggerProps }) => (
    <a aria-label={ariaLabel} className={className} href="/home" {...triggerProps}>
      {children}
    </a>
  )}
>
  <HomeIcon />
</IconButton>
```

### Card

Floating on a shadow is the default: a white card over a `bg-subtle` page.

```tsx
import { Card } from '@k8ordo/ui';

// Static card (floated with a shadow)
<Card variant="shadow">
  <div className="p-8">Card content</div>
</Card>

// Clickable card (interactive scales it up on hover)
<Card variant="shadow" interactive>
  <div className="p-8">Content</div>
</Card>
```

### Forms

`FormControl` plus each form component's `renderInput` pattern.

```tsx
import {
  Button,
  FileField,
  FormControl,
  Select,
  TextField,
} from '@k8ordo/ui';

<FormControl label="Email" required renderInput={(props) => (
  <TextField {...props} placeholder="example@mail.com" />
)} />

<FormControl label="Category" renderInput={(props) => (
  <Select
    {...props}
    options={[{ value: '1', label: 'Option 1' }]}
    value={value}
    onChange={onChange}
  />
)} />

<FileField.Root accept="image/*" multiple>
  <FileField.Trigger
    renderItem={({ onClick, disabled }) => (
      <Button disabled={disabled} onClick={onClick}>Choose files</Button>
    )}
  />
  <FileField.ItemList />
</FileField.Root>
```

## Anti-patterns: avoiding "AI slop"

Avoid the traits that make a UI recognizably AI-generated at a glance.

| Anti-pattern               | The @k8ordo/ui alternative                         |
| -------------------------- | -------------------------------------------------- |
| Purple gradients           | Flat teal/cyan color                               |
| Card in Card               | A Separator and spacing instead                    |
| Grey text on a grey ground | Hold contrast with `text-fg-base` / `text-fg-mute` |
| Bounce / spring animation  | `transition-colors duration-150 ease-out`          |
| The Inter typeface         | Noto Sans JP / M PLUS 2                            |
| Heavy glassmorphism        | A border plus a subtle background color            |
| Cramming information in    | Sparse placement that lets the space breathe       |

### The AI slop test

> Show this UI to someone and tell them an AI made it. Would they believe it
> immediately? If yes, that is the problem.

## Implementation principles

- **Use the existing components**: look for a @k8ordo/ui component before building custom UI
- **Render from Server Components**: every component, compound ones included (`Dialog.Root`, `Tabs.Root`, …), can be placed in a Server Component; only the interactive parts are client modules
- **Keep placed state in `@k8ordo/state`**: state that lives in the URL, a history entry, localStorage, or memory is `defineLocalState` and friends, not a hook from this package
- **Use semantic tokens**: tokens (`bg-primary-bg`), never raw color values (`bg-teal-500`)
- **Let space and shape carry it**: character comes from spacing and soft radii, not from vivid color
- **Do not forget dark mode**: semantic tokens handle it for you
- **Accessibility**: `aria-label`, keyboard navigation, and state that does not rely on color alone

## Detailed reference

- Typography: [references/typography.md](references/typography.md)
- Color system: [references/color.md](references/color.md)
- Spacing and layout: [references/spatial-design.md](references/spatial-design.md)
- Interaction: [references/interaction-design.md](references/interaction-design.md)
- Component catalog: [references/components.md](references/components.md)
- Hooks: [references/hooks.md](references/hooks.md)
- Helpers and types: [references/helpers.md](references/helpers.md)
- AI chat (Conversation / Message / PromptInput, …): [references/ai-chat.md](references/ai-chat.md)
- Generative UI (having an LLM generate UI via json-render / OpenUI): [references/generative-ui.md](references/generative-ui.md)
