@gradeui/ui 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: Banner
3
+ import: "@gradeui/ui"
4
+ variants: [default, info, success, warning, destructive, announcement]
5
+ props:
6
+ - variant? (default | info | success | warning | destructive | announcement) — intent + tonal direction. `default` is a calm muted strip; `announcement` is a low-alpha brand tint for "new feature" messaging; status variants pick up the soft+deep token pairs.
7
+ - surface? (solid | translucent | glass | glass-strong) — material applied over the variant tint. `glass` for banners that sit over imagery / generative backdrops.
8
+ - align? (start | center | between) — justify behaviour of the inner flex row. Defaults to `between` so the action / dismiss button right-align.
9
+ - sticky?: boolean — stick to the top of the scroll container.
10
+ - dismissible?: boolean — render the trailing X close button. Pair with `onDismiss` to react.
11
+ - onDismiss?: () => void
12
+ - icon?: ReactNode — leading icon slot. NOT inferred from variant; pass what fits the message.
13
+ - action?: ReactNode — trailing slot before dismiss. Usually a `<Button size="sm">` or `<a>`.
14
+ - role?: string — overrides the automatic role mapping (warning/destructive → alert, others → status).
15
+ when_to_use: A full-width horizontal strip surfacing system-level state, announcements, or first-run guidance — "you're previewing a draft", "investigating incident", "new feature available", "send your design to Figma". Distinct from Callout (inline boxed message in the layout flow), Toast (transient floating notification), Dialog (modal interrupt). Banner is what lives at the TOP of an AppShellHeader, page, or panel.
16
+ composes_with: [AppShellHeader (most common host — banner sits ABOVE the header content), Button (in the action slot), Link (inside the content), Lucide icons (in the icon slot)]
17
+ aliases: [banner, notification banner, system banner, header banner, announcement bar, top bar, status bar, promo banner, incident banner, draft banner, first run banner, glass banner, sticky banner]
18
+ ---
19
+
20
+ Banner is the "horizontal strip across the top of something" primitive. The shape difference from Callout matters: Callout is an inline boxed message inside layout flow; Banner is full-bleed and meant to anchor at the top of a page, panel, or AppShellHeader.
21
+
22
+ ---
23
+
24
+ ### Scenario 1 — First-run guidance (default)
25
+
26
+ A one-line hint surfaced the first time a user lands on a tab or screen. Calm muted tint, dismissible, lives above the main content.
27
+
28
+ ```jsx
29
+ <Banner
30
+ variant="default"
31
+ dismissible
32
+ onDismiss={dismiss}
33
+ action={
34
+ <a href={pluginUrl} target="_blank" rel="noreferrer" className="text-sm font-medium underline underline-offset-4">
35
+ Get the Grade plugin →
36
+ </a>
37
+ }
38
+ >
39
+ Send your design to Figma as live components.
40
+ </Banner>
41
+ ```
42
+
43
+ This is the canonical replacement for the inline-style `FigmaIntroBanner` that motivated this primitive — same content, but the tint inherits properly from the active theme and the dismiss button gets the same focus-ring treatment as every other interactive element.
44
+
45
+ ---
46
+
47
+ ### Scenario 2 — Incident / warning banner at AppShellHeader top
48
+
49
+ You need to tell users something is going wrong without interrupting them. Banner with `variant="warning"` (or `destructive` if it's worse) sits at the very top of the AppShell.
50
+
51
+ ```jsx
52
+ <AppShell>
53
+ <Banner
54
+ variant="warning"
55
+ sticky
56
+ icon={<AlertTriangle className="h-4 w-4" />}
57
+ action={
58
+ <Button asChild variant="outline" size="sm">
59
+ <a href="/status">Status page</a>
60
+ </Button>
61
+ }
62
+ >
63
+ We're investigating an incident affecting search results. Comments and edits are unaffected.
64
+ </Banner>
65
+ <AppShellHeader>...</AppShellHeader>
66
+ <AppShellMain>...</AppShellMain>
67
+ </AppShell>
68
+ ```
69
+
70
+ `sticky` so it doesn't scroll away (incidents stay visible). `variant="warning"` gets `role="alert"` automatically — screen readers interrupt to announce it.
71
+
72
+ ---
73
+
74
+ ### Scenario 3 — Announcement banner (brand tint, low-key)
75
+
76
+ New feature announcement. You want to be noticed but not alarming. The `announcement` variant uses a low-alpha brand tint so the banner reads as "we have news" without competing with the page.
77
+
78
+ ```jsx
79
+ <Banner
80
+ variant="announcement"
81
+ dismissible
82
+ onDismiss={dismissAnnouncement}
83
+ icon={<Sparkles className="h-4 w-4" />}
84
+ action={
85
+ <Button asChild size="sm">
86
+ <a href="/components/code">See how →</a>
87
+ </Button>
88
+ }
89
+ >
90
+ <strong className="font-medium">New —</strong> Code component lands with diff hero and scroll-triggered reveals.
91
+ </Banner>
92
+ ```
93
+
94
+ ---
95
+
96
+ ### Scenario 4 — Glass banner over a hero image
97
+
98
+ The marketing site has a hero image and a top banner promoting an event. A solid banner would punch a stripe through the imagery. Glass keeps the image visible.
99
+
100
+ ```jsx
101
+ <div className="relative h-screen" style={{ backgroundImage: "url(/hero/teams-shipping.jpg)", backgroundSize: "cover" }}>
102
+ <Banner
103
+ surface="glass"
104
+ sticky
105
+ align="center"
106
+ action={
107
+ <Button size="sm" variant="outline" asChild>
108
+ <a href="/launchweek">Watch the launch →</a>
109
+ </Button>
110
+ }
111
+ >
112
+ GradeUI launch week kicks off 14 June.
113
+ </Banner>
114
+ ...
115
+ </div>
116
+ ```
117
+
118
+ `surface="glass"` + the default `variant="default"` gives a frosted strip with `--surface-blur-glass` worth of blur. Pair with `align="center"` when the banner has no leading icon — keeps the message visually centered.
119
+
120
+ ---
121
+
122
+ ### Anti-patterns
123
+
124
+ **DO NOT roll a banner with inline styles or Tailwind soup.**
125
+
126
+ ```jsx
127
+ {/* ❌ Wrong token names, no dismiss focus ring, no role mapping, no theme inheritance. */}
128
+ <div style={{ background: "oklch(var(--gds-primary) / 0.06)", color: "var(--gds-foreground)" }}>
129
+ Send your design to Figma. <a>Get the Grade plugin →</a>
130
+ </div>
131
+
132
+ {/* ✅ */}
133
+ <Banner variant="announcement" dismissible onDismiss={dismiss} action={...}>
134
+ Send your design to Figma.
135
+ </Banner>
136
+ ```
137
+
138
+ The inline-style original this primitive replaced was effectively invisible because it reached for `--gds-primary` / `--gds-foreground` tokens that don't exist. The fallback values kicked in and the banner washed out completely. Banner exists so this category of mistake is impossible.
139
+
140
+ **DO NOT use Banner for inline form-level validation.** That's Callout's job — it's a boxed message inside the layout flow. Banner is full-bleed chrome.
141
+
142
+ **DO NOT use Banner for transient confirmation ("Saved").** That's Toast (Sonner). Banner is persistent until dismissed.
143
+
144
+ **DO NOT stack multiple Banners.** Two banners reading at the same time fight for attention. If you genuinely need two messages, surface the highest-priority one and queue the second for after the first is dismissed.
145
+
146
+ **DO NOT pass `role="alert"` on a calm `variant="info"` Banner.** The variant→role mapping is intentional. Info/success/announcement are polite; warning/destructive interrupt. Overriding makes assistive tech behaviour inconsistent with the visual signal.
@@ -3,14 +3,26 @@ name: Card
3
3
  import: "@gradeui/ui"
4
4
  subcomponents: [CardHeader, CardTitle, CardDescription, CardContent, CardFooter]
5
5
  props:
6
+ - surface? (solid | translucent | glass | glass-strong) — what the card surface is *made of*. `solid` is the default opaque `bg-card`. `translucent` is ~82% opacity for menu sheets. `glass` is ~58% opacity + 14px blur + edge highlight for floating panels. `glass-strong` is ~42% + 24px blur for full-page overlays. Composes with `shadow-elevation-*` (depth) and `gds-aura-*` (state signal).
6
7
  - Each subcomponent accepts native div HTML attrs (className, etc.)
7
- - No variants Card is a flexible container surface; shape via data-card-style and depth via shadow-elevation-* utilities
8
- when_to_use: Grouped content with a distinct surface — settings panels, dashboard tiles, list-of-cards layouts. Pair CardHeader (title + description) with CardContent and optional CardFooter (actions).
9
- composes_with: [Button (in CardFooter), Badge, Separator, Avatar, any form controls]
10
- aliases: [card, group box, groupbox, panel, tile, surface]
8
+ when_to_use: Grouped content with a distinct surface settings panels, dashboard tiles, list-of-cards layouts, marketing hero containers, AI suggestion overlays. Pair CardHeader (title + description) with CardContent and optional CardFooter (actions). Reach for `surface="glass"` whenever the card sits over a busy backdrop (gradient mesh, dot grid, generative art, image hero).
9
+ composes_with: [Button (in CardFooter), Badge, Separator, Avatar, Code, MediaSurface, any form controls]
10
+ aliases: [card, group box, groupbox, panel, tile, surface, glass card, frosted card, floating panel, hero card, ai suggestion card, dashboard tile, settings panel]
11
11
  ---
12
12
 
13
- Canonical structure do NOT skip CardHeader if the card has a title:
13
+ Card is the most common host for the **Presence** system (PRESENCE.md). Three independent axes layer on top of every card:
14
+
15
+ - **Surface** — what it's made of (`surface` prop: solid / translucent / glass / glass-strong)
16
+ - **Elevation** — how high it sits (`shadow-elevation-1..5` utility)
17
+ - **Aura** — what it's radiating (`gds-aura-ring`, `gds-aura-gradient`, `gds-aura-shimmer`)
18
+
19
+ The four scenarios below are the canonical recipes. Match the scenario to the screen you're building.
20
+
21
+ ---
22
+
23
+ ### Scenario 1 — Settings panel (default opaque)
24
+
25
+ You want a grouped content surface on a normal page: a settings panel, a list-of-cards tile, a dashboard widget. The page background is calm; the card just needs to sit cleanly on it.
14
26
 
15
27
  ```jsx
16
28
  <Card>
@@ -18,23 +30,166 @@ Canonical structure — do NOT skip CardHeader if the card has a title:
18
30
  <CardTitle>Billing</CardTitle>
19
31
  <CardDescription>Manage your subscription.</CardDescription>
20
32
  </CardHeader>
21
- <CardContent>…</CardContent>
33
+ <CardContent>
34
+ <Stack gap="md">
35
+ <Row justify="between">
36
+ <span className="text-sm">Plan</span>
37
+ <Badge>Pro</Badge>
38
+ </Row>
39
+ <Row justify="between">
40
+ <span className="text-sm">Renews</span>
41
+ <span className="text-sm text-muted-foreground">12 Jun 2026</span>
42
+ </Row>
43
+ </Stack>
44
+ </CardContent>
22
45
  <CardFooter>
23
- <Button>Save</Button>
46
+ <Button variant="outline">Cancel plan</Button>
47
+ <Button>Update payment</Button>
24
48
  </CardFooter>
25
49
  </Card>
26
50
  ```
27
51
 
28
- Card is the most common host for Presence affordances. Three independent axes:
52
+ No `surface` prop — the default `solid` is the right answer for almost every in-page card. Reach for glass only when there's something behind worth blurring.
53
+
54
+ ---
55
+
56
+ ### Scenario 2 — Glass card over a busy backdrop (marketing hero)
57
+
58
+ You're building a marketing hero. There's a gradient mesh, a dot grid, generative art, or a hero image behind the card. The card should read as **floating chrome** — translucent enough to let the backdrop breathe through, but with a defined edge so the content stays legible.
29
59
 
30
60
  ```jsx
31
- // Elevation — pick a depth level (1=minimal, 3=raised, 4=popover, 5=dialog).
32
- <Card className="shadow-elevation-4">…</Card>
61
+ <SectionBlock background="gradient" padding="xl">
62
+ <Grid cols="2" gap="md">
63
+ <Card surface="glass" className="shadow-elevation-4">
64
+ <CardHeader>
65
+ <CardTitle>v0 — sidebar component</CardTitle>
66
+ <CardDescription>~300 lines</CardDescription>
67
+ </CardHeader>
68
+ <CardContent className="p-0">
69
+ <Code source={v0Code} language="tsx" bare className="p-4 text-xs max-h-72" />
70
+ </CardContent>
71
+ </Card>
72
+
73
+ <Card surface="glass" className="shadow-elevation-4 gds-aura-ring">
74
+ <CardHeader>
75
+ <CardTitle>GradeUI — sidebar component</CardTitle>
76
+ <CardDescription>6 lines</CardDescription>
77
+ </CardHeader>
78
+ <CardContent className="p-0">
79
+ <Code source={gradeCode} language="tsx" bare className="p-4 text-xs max-h-72" />
80
+ </CardContent>
81
+ </Card>
82
+ </Grid>
83
+ </SectionBlock>
84
+ ```
85
+
86
+ `surface="glass"` does five things at once: 58% opacity `bg-card`, 14px backdrop blur, an inner edge highlight (the "wet" rim that gives glass its boundary), a faint border, and it drops the base `bg-card` so the alpha actually shows. Layering `shadow-elevation-4` adds the floating-popover drop shadow; `gds-aura-ring` makes the second card pulse with a blue halo to signal "this is the recommended path".
87
+
88
+ ---
89
+
90
+ ### Scenario 3 — Translucent menu sheet (floating chrome with structure)
33
91
 
34
- // Surfaceopt into glass / translucent backgrounds.
35
- <Card className="gds-surface-glass shadow-elevation-4">…</Card>
92
+ You want a floating panel a command palette, a notification drawer, an AI suggestion overlay — that's visibly distinct from the canvas but doesn't need full glass blur. Translucent is for "I want presence without drama".
36
93
 
37
- // Aura — radiate AI-attention state. Combinable.
38
- <Card className="gds-aura-ring">Studio is reviewing this</Card>
39
- <Card className="gds-aura-ring gds-aura-shimmer">Generating…</Card>
94
+ ```jsx
95
+ <Card surface="translucent" className="shadow-elevation-5 w-80">
96
+ <CardHeader>
97
+ <CardTitle>Suggested action</CardTitle>
98
+ <CardDescription>Studio noticed a layout opportunity.</CardDescription>
99
+ </CardHeader>
100
+ <CardContent>
101
+ <p className="text-sm">
102
+ Three buttons in your toolbar would line up edge-to-edge with the
103
+ tabs below if their size matched. Apply <code>size="sm"</code>?
104
+ </p>
105
+ </CardContent>
106
+ <CardFooter>
107
+ <Button variant="ghost" size="sm">Dismiss</Button>
108
+ <Button size="sm">Apply</Button>
109
+ </CardFooter>
110
+ </Card>
40
111
  ```
112
+
113
+ 82% opacity is enough to feel layered but not enough to need backdrop blur — works equally well over a busy or a calm background. `shadow-elevation-5` (dialog tier) plus `translucent` is the "floating but not glass" signature.
114
+
115
+ ---
116
+
117
+ ### Scenario 4 — AI is generating (aura + surface composition)
118
+
119
+ You want to signal that Studio (or any AI agent) is actively working on this card. Aura is the right axis for state signals. It composes with any surface.
120
+
121
+ ```jsx
122
+ <Card
123
+ surface="glass"
124
+ className="shadow-elevation-4 gds-aura-ring gds-aura-shimmer"
125
+ style={{ "--aura-color": "var(--selected-glow)" }}
126
+ >
127
+ <CardHeader>
128
+ <CardTitle>Generating layout</CardTitle>
129
+ <CardDescription>About 4 seconds remaining.</CardDescription>
130
+ </CardHeader>
131
+ <CardContent>
132
+ <Stack gap="xs">
133
+ <Skeleton className="h-4 w-3/4" />
134
+ <Skeleton className="h-4 w-1/2" />
135
+ <Skeleton className="h-4 w-2/3" />
136
+ </Stack>
137
+ </CardContent>
138
+ </Card>
139
+ ```
140
+
141
+ Ring (pulsing halo) + shimmer (diagonal sweep) together = "actively generating". For "Studio is reviewing this", use ring alone. For "ready to ship", swap tone to `--success`. The skeletons inside are the content's own loading state — orthogonal to the card-level aura.
142
+
143
+ ---
144
+
145
+ ### Scenario 5 — Glass-strong for a full-page overlay backdrop
146
+
147
+ `surface="glass-strong"` is tuned for a different job than the other three: it's the **backdrop** behind a modal sheet, not the modal itself. Heavy blur (24px), 42% opacity. Use it to de-emphasise the page underneath while keeping it readable.
148
+
149
+ ```jsx
150
+ <Card surface="glass-strong" className="fixed inset-4 z-50">
151
+ <CardContent className="grid place-items-center h-full">
152
+ <Stack gap="md" align="center">
153
+ <Spinner />
154
+ <span className="text-lg">Saving your theme…</span>
155
+ </Stack>
156
+ </CardContent>
157
+ </Card>
158
+ ```
159
+
160
+ Almost always wrong for in-flow content — at 42% opacity the card reads as washed out. If you find yourself reaching for glass-strong for a regular card, you probably want `glass`.
161
+
162
+ ---
163
+
164
+ ### Anti-patterns
165
+
166
+ **DO NOT roll glass by hand with Tailwind utilities.** The wrong path:
167
+
168
+ ```jsx
169
+ {/* ❌ Tailwind soup — misses edge highlight, locks blur to a fixed step,
170
+ bypasses theme tuning, no Studio inspector knob. */}
171
+ <Card className="overflow-hidden border-border bg-card/40 backdrop-blur-md">
172
+ ```
173
+
174
+ The right path:
175
+
176
+ ```jsx
177
+ {/* ✅ Theme-aware bg, tuned blur, edge highlight, knob-discoverable. */}
178
+ <Card surface="glass">
179
+ ```
180
+
181
+ This is the single most common mistake. The model reaches for `bg-card/40 backdrop-blur-md` because every other DS leaves glass at the utility layer. Ours doesn't.
182
+
183
+ **DO NOT layer a solid `bg-card` className over `surface="glass"`.** The opaque fill defeats the blur. Card already drops `bg-card` when `surface` is set to anything other than `solid` — don't undo that by tacking `bg-card` back on via className. If you want a tinted glass, override `--card` on the element:
184
+
185
+ ```jsx
186
+ <Card surface="glass" style={{ "--card": "0.99 0.04 250" }}>
187
+ ...
188
+ </Card>
189
+ ```
190
+
191
+ **DO NOT use `surface="glass"` over a solid background.** Glass needs something behind it to blur. Over plain `bg-background` it reads as a slightly washed-out card and you pay for backdrop-filter for no gain. If the page is calm, use `solid`.
192
+
193
+ **DO NOT use `surface="glass-strong"` for in-flow content.** It's a full-page overlay material. At 42% opacity, regular cards read as washed out. Reach for `glass`.
194
+
195
+ **DO NOT skip CardHeader if the card has a title.** The header is the semantic anchor for the title + description pair. Inline `<h3>` inside CardContent breaks the visual rhythm and harms screen-reader navigation.
@@ -0,0 +1,133 @@
1
+ ---
2
+ name: Code
3
+ import: "@gradeui/ui"
4
+ variants: []
5
+ props:
6
+ - source: string — the code to render
7
+ - language? (tsx | jsx | ts | js | html | css | json | bash | md | py | go | rust) — Prism language id; defaults to `tsx`
8
+ - highlight? — 1-indexed line number, array of numbers, or array of `[start, end]` ranges to emphasise
9
+ - diff? — `{ added?: number[]; removed?: number[] }` — 1-indexed lines for diff hero / changelog mode
10
+ - reveal? (none | lines | typewriter | diff) — entrance animation; defaults to `none`
11
+ - trigger? (mount | inView | manual) — what kicks the reveal off; defaults to `mount`
12
+ - play?: boolean — for `trigger="manual"`, set true to play
13
+ - speed? (slow | normal | fast) — animation feel preset. `normal` (default) maps to the canonical 50ms/22ms staggers + 180ms pre-delay. Pick a feel; don't tune individual numbers unless you have to.
14
+ - delay?: number — explicit delay before reveal starts (ms) — overrides the `speed` preset
15
+ - stagger?: number — explicit per-line stagger for `lines`/`diff`, per-token for `typewriter` (ms) — overrides the `speed` preset
16
+ - prompt?: string — string prepended to each line. Use for terminal emulation: `prompt="$ "` for bash, `prompt="> "` for PowerShell, `prompt=">>> "` for Python REPL. Prompt characters render in muted token colour, don't pick up the typewriter stagger, and are hidden from screen readers.
17
+ - showLineNumbers?: boolean
18
+ - filename?: string — optional label rendered in the header chrome
19
+ - wrap?: boolean — wrap long lines instead of horizontal scroll
20
+ - bare?: boolean — drop chrome (border, header, padding) — for inline use
21
+ - size? (xs | sm | md) — type-scale preset. `xs` (12px) for dense changelog cards / inline blocks; `sm` (14px, default) for marketing heroes and docs; `md` (16px) for focal-point displays.
22
+ - height? (auto | number | string) — container sizing. `auto` (default) grows with content. Number = pixels (`300` → `300px`). String passes through as CSS (`"20rem"`, `"50vh"`).
23
+ - maxLines?: number — cap the visible line count at exactly N line-heights. Wins over `height`. Inherits the current size's line-height automatically.
24
+ when_to_use: Read-only code surface for marketing heroes, docs, changelog entries, AI-output displays. Use `diff` for the "diff hero" pattern (before/after side-by-side or stacked). Use `reveal="lines"` with `trigger="inView"` for scroll-driven marketing pages. Use `reveal="typewriter"` for AI-output / chat-style displays. Use `bare` for inline code inside prose. NOT a code editor — for editable code, reach for an external editor primitive (CodeMirror / Monaco).
25
+ composes_with: [SectionBlock, Card, Tabs (for multi-file examples), Carousel (slide-to-slide code progression)]
26
+ aliases: [code block, code, code snippet, code surface, syntax highlighted code, diff hero, diff view, diff block, changelog code, before after code, scroll-triggered code, typewriter code]
27
+ ---
28
+
29
+ Token palette is driven by `--gds-code-*` CSS variables in `styles/globals.css`. The component itself is presentation-agnostic: prism renders tokens, the variables colour them, motion handles the reveal. Override per-instance via inline `style` to retune one block without touching the theme.
30
+
31
+ The engine is `prism-react-renderer` (already used by Studio's CodeView). Sync, ~6kb, render-prop API — no async hydration flash, no bundle bloat from lang files.
32
+
33
+ ```jsx
34
+ // Plain block.
35
+ <Code language="tsx" source={`function greet(name) {
36
+ return \`Hello, \${name}\`;
37
+ }`} />
38
+ ```
39
+
40
+ ```jsx
41
+ // Line highlight — emphasis lines accept a number, an array, or
42
+ // [start, end] ranges. Composes cleanly with diff (diff colours win).
43
+ <Code
44
+ language="tsx"
45
+ highlight={[2, [4, 6]]}
46
+ source={`<Button>Save</Button>
47
+ <Button variant="raised">Ship it</Button>
48
+ <Button variant="raised" style={{ "--btn-glow": "var(--warning)" }}>
49
+ Iterate
50
+ </Button>`}
51
+ />
52
+ ```
53
+
54
+ ```jsx
55
+ // Diff hero — the marketing "before / after" pattern. Added lines get
56
+ // the success-tinted bg + `+` gutter; removed lines get destructive-
57
+ // tinted bg + `-` gutter. `showLineNumbers` is opt-in.
58
+ <Code
59
+ language="tsx"
60
+ filename="button.tsx"
61
+ diff={{ removed: [1], added: [2, 3, 4] }}
62
+ source={`<button className="px-4 py-2 rounded-md bg-blue-600 text-white shadow-md">
63
+ <Button variant="raised">
64
+ Ship it
65
+ </Button>`}
66
+ />
67
+ ```
68
+
69
+ ```jsx
70
+ // Scroll-triggered reveal — marketing hero. The block waits until the
71
+ // reader scrolls it into view, then staggers each line in. `once: true`
72
+ // is baked in: the reveal doesn't replay when the user scrolls away
73
+ // and back.
74
+ <Code
75
+ language="tsx"
76
+ reveal="lines"
77
+ trigger="inView"
78
+ stagger={50}
79
+ source={`<AppShell nav="three-pane">
80
+ <AppShellHeader>...</AppShellHeader>
81
+ <AppShellNav>...</AppShellNav>
82
+ <AppShellAside>...</AppShellAside>
83
+ <AppShellMain>...</AppShellMain>
84
+ </AppShell>`}
85
+ />
86
+ ```
87
+
88
+ ```jsx
89
+ // Typewriter — token-by-token reveal. Good for AI-output displays
90
+ // and "watch it generate" demos. Whitespace tokens are free (no
91
+ // delay) so leading indent doesn't feel like dead time. `speed`
92
+ // keeps it ergonomic — pick "slow" / "normal" / "fast" instead of
93
+ // tuning stagger by hand.
94
+ <Code
95
+ language="tsx"
96
+ reveal="typewriter"
97
+ trigger="inView"
98
+ speed="normal"
99
+ source={`const theme = await ai.generate({
100
+ brand: "Acme",
101
+ mood: "calm",
102
+ });`}
103
+ />
104
+ ```
105
+
106
+ ```jsx
107
+ // Terminal emulation — `prompt` prepends a static prompt string to
108
+ // each line. Combine with `reveal="typewriter"` for a scripted CLI
109
+ // session feel. Prompt chars are chrome (muted, aria-hidden, no
110
+ // animation), so the typewriter only stages the actual command.
111
+ <Code
112
+ language="bash"
113
+ prompt="$ "
114
+ reveal="typewriter"
115
+ trigger="inView"
116
+ speed="slow"
117
+ source={`pnpm add @gradeui/ui
118
+ pnpm gradeui init
119
+ pnpm dev`}
120
+ />
121
+ ```
122
+
123
+ ### Anti-patterns
124
+
125
+ DO NOT use `<Code>` as a code editor. It's read-only by design — the prism renderer doesn't take input. For editable code, compose your own surface around CodeMirror or Monaco.
126
+
127
+ DO NOT reach for a separate library to render code elsewhere in the app — Studio's CodeView, docs blocks, and marketing heroes should all share this primitive so the token palette stays single-source.
128
+
129
+ DO NOT pass `highlight` AND `diff` for the same line — diff wins, and the highlight emphasis is silently dropped. Use one signal per line.
130
+
131
+ DO NOT use `reveal="typewriter"` for long blocks (50+ lines). It works but feels laboured; use `reveal="lines"` instead.
132
+
133
+ DO NOT override the prism `theme` prop — the component intentionally hides it. Restyle via the `--gds-code-*` CSS variables so every Code block in the app shifts together with the active theme.