# Editorial — Theme for React

> A self-contained glossy-magazine theme preset — a single-column feature with a small-caps kicker and issue rule, a high-contrast serif display headline, an italic standfirst, a small-caps byline, a drop-cap opening, a hairline-barred pull-quote, and a pair of buttons, all breathing in generous whitespace with one disciplined ink accent. A keyboard-operable radiogroup of surface chips re-tokens the whole page live (warm Ivory, cool Slate, dim night-reading Ink); the only motion is a soft CSS color crossfade on switch, and reduced motion makes every swap instant and fully static.

- Page: https://crucibleui.com/components/themes/editorial
- Category: [themes](https://crucibleui.com/components/themes)
- Tier: pro (one-time purchase, see https://crucibleui.com/pricing)
- Engine: css

## Install

```bash
npx shadcn@latest add @crucible/editorial
```

Pro install requires adding the token-bearing `@crucible` registry to components.json (issued at https://crucibleui.com/account after purchase).

Import from `@/components/crucible/editorial`.

## Props (`EditorialProps`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant?` | `string` | — | Controlled active preset id (matches a preset's `id`). |
| `defaultVariant?` | `string` | `"ivory" (first preset)` | Initial preset id when uncontrolled. |
| `onVariantChange?` | `(id: string) => void` | — | Fires with the next preset id whenever the switcher changes. |
| `presets?` | `EditorialPreset[]` | `the three built-in surfaces` | Override the built-in reading surfaces (chips + palettes). |
| `colors?` | `Partial<EditorialPalette>` | — | Token overrides merged over the active preset — makes any palette reachable. |
| `duration?` | `number` | `0.55` | Seconds the palette crossfade takes when switching surfaces. |
| `paused?` | `boolean` | `false` | Freeze the palette crossfade — surfaces swap instantly. |
| `kicker?` | `string` | `"The Essay"` | Small-caps kicker above the headline. |
| `issue?` | `string` | `"Issue 04"` | Issue marker printed at the top rule. |
| `heading?` | `string` | `"The Quiet Craft of Slow Mornings"` | Serif display headline. |
| `standfirst?` | `string` | `a short generic standfirst` | Italic standfirst under the headline. |
| `byline?` | `string` | `"Words by A. Marlowe"` | Small-caps byline. |
| `readingTime?` | `string` | `"6 min read"` | Reading-time meta shown beside the byline. |
| `body?` | `string[]` | `two generic paragraphs` | Body paragraphs. The first receives the drop-cap; the pull-quote follows it. |
| `pullQuote?` | `string` | `a generic aphorism` | Large italic pull-quote set between the body paragraphs. |
| `primaryLabel?` | `string` | `"Read the essay"` | Filled button label. |
| `secondaryLabel?` | `string` | `"Save"` | Hairline-outline button label. |
| `label?` | `string` | `"Editorial theme preset"` | Accessible label for the panel region. |
| `switcherLabel?` | `string` | `"Reading surface"` | Accessible label for the surface switcher group. |

Also accepts: `Omit<React.ComponentPropsWithoutRef<"article">, "onChange">`.

## Usage example

```tsx
"use client";

import * as React from "react";

import { Editorial } from "@/components/crucible/editorial";

export default function EditorialDemo() {
  const [variant, setVariant] = React.useState("ivory");

  return (
    <div className="relative isolate flex h-full min-h-[360px] w-full items-center justify-center overflow-hidden rounded-xl bg-neutral-950 py-10">
      <div
        aria-hidden
        className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(255,255,255,0.04),transparent_70%)]"
      />
      <div className="relative z-10 flex w-full flex-col items-center gap-4 px-6">
        <Editorial variant={variant} onVariantChange={setVariant} />
        <p className="text-center text-sm text-neutral-500">
          Switch the look — click a surface chip, or focus it and use the arrow keys.
        </p>
      </div>
    </div>
  );
}
```

## Accessibility & performance

Honors `prefers-reduced-motion` with a designed static frame or fade (never a blank box) and passes `className` through.
