# Concepts

The mental model behind framecn UI primitives — state atoms, transition hooks, theming, and core exports

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. An agent skill is available at [/.well-known/agent-skills/site-skill.md](/.well-known/agent-skills/site-skill.md).

## State atoms

**State atoms** (Button, Input, Checkbox, Switch) are pure visual functions of `(state | style, theme)`. They read no clock internally and snap instantly between named states when you pass `state`. Pass `from` plus `duration` to animate between states via CSS keyframes generated by the transition hook.

**Purity boundary:** framecn UI components never call browser APIs or hold internal React state for interaction. Timeline resolution happens in transition hooks (`use-button-transition.ts`, etc.) or via explicit `state` / `from` props on the component.

### Motion atoms

**Motion atoms** (Spinner, Caret, TypingIndicator) loop on the timeline with no discrete states. They are safe in Editframe's headless renderer — no `Date`, no `Math.random`, no `requestAnimationFrame`.

## Transition hooks

Each state atom ships a **transition file** (e.g. `use-button-transition.ts`) copied into your project alongside the component. That file:

- owns default duration and easing
- exports keyframe helpers and style presets per state
- resolves `from` → `state` animation timing for the `Timegroup`

To get smooth motion, pass `from` and `state` with a `duration`:

```tsx
import { Button } from "@/components/framecn/button";

export function Scene() {
  return (
    <Button from="idle" state="loading" duration="12frames" label="Continue" />
  );
}
```

To tune timing globally, edit `DEFAULT_DURATION` in your copied `use-button-transition.ts`. To tune easing, edit the easing call in the same file.

## States snap by default

Without `from`, state atoms snap instantly between named states. Snap is the default path: pass `state="hover"` and the atom jumps straight to that visual.

## Theming

The UI tier uses a JS theme object with stock shadcn token names. Values are concrete oklch strings — not CSS custom properties.

```ts
import type { FramecnTheme } from "@/lib/framecn-ui";

const myTheme: Partial<FramecnTheme> = {
  primary: "oklch(0.55 0.2 250)",
  radius: 6,
};
```

### `FramecnUIProvider`

Wrap your composition in `FramecnUIProvider` to set a theme and/or mode for all nested UI primitives:

```tsx
import { FramecnUIProvider } from "@/lib/framecn-ui";

export function Scene() {
  return (
    <FramecnUIProvider mode="dark" theme={{ primary: "oklch(0.6 0.22 260)" }}>
      <Button state="idle" label="Continue" />
    </FramecnUIProvider>
  );
}
```

Token resolution order (highest wins): per-component `theme` prop → `FramecnUIProvider` theme → `defaultLightTheme` / `defaultDarkTheme` per `mode`.

Available exports from `@/lib/framecn-ui`:

| Export              | Description                                  |
| ------------------- | -------------------------------------------- |
| `FramecnUIProvider` | Context provider for theme and mode          |
| `useFramecnTheme`   | Resolves the active theme inside a component |
| `defaultLightTheme` | Stock shadcn neutral light palette           |
| `defaultDarkTheme`  | Stock shadcn neutral dark palette            |
| `FramecnTheme`      | Full token shape with `radius: number`       |

### Animated colors require concrete JS values

CSS custom properties (`var(--primary)`) work for **static** styling only. Editframe's per-frame renderer cannot resolve `var(...)` for JS color interpolation. Pass concrete oklch, hex, or rgb values via the `theme` prop or `FramecnUIProvider` for animated tokens.

## Core library exports

Installing any UI component also installs `lib/framecn-ui/` with these modules:

| Module        | Key exports                                                                     |
| ------------- | ------------------------------------------------------------------------------- |
| `timeline.ts` | `framesFor`, `revealCount`, `revealedText`, `useTypewriter`                     |
| `theme.ts`    | `FramecnUIProvider`, `useFramecnTheme`, `defaultLightTheme`, `defaultDarkTheme` |
| `color.ts`    | `mixOklch`, `parseColor`, `oklchToRgb`, `rgbToOklch`, `toCss`                   |
| `motion.ts`   | `easings`, `springs`                                                            |
| `types.ts`    | `Step`                                                                          |

You can import from `@/lib/framecn-ui` (the barrel) or from the individual module paths.

## Blocks

**Blocks** are multi-step UI flows (ChatFlow, SignupFlow, CheckoutFlow) composed from state atoms. They orchestrate several primitives in sequence on a shared timeline — install them the same way as components via `@framecn/<name>`.