For the complete documentation index, see 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.
/
144
Sponsor

Concepts

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

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:

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.

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:

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:

ExportDescription
FramecnUIProviderContext provider for theme and mode
useFramecnThemeResolves the active theme inside a component
defaultLightThemeStock shadcn neutral light palette
defaultDarkThemeStock shadcn neutral dark palette
FramecnThemeFull 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:

ModuleKey exports
timeline.tsframesFor, revealCount, revealedText, useTypewriter
theme.tsFramecnUIProvider, useFramecnTheme, defaultLightTheme, defaultDarkTheme
color.tsmixOklch, parseColor, oklchToRgb, rgbToOklch, toCss
motion.tseasings, springs
types.tsStep

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>.