@gradeui/ui 1.0.0 → 1.1.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.
@@ -6,17 +6,26 @@ props:
6
6
  - DropdownMenu: open?, defaultOpen?, onOpenChange?, modal? (default true)
7
7
  - DropdownMenuTrigger: asChild?: boolean — usually wraps a Button
8
8
  - DropdownMenuContent: align? "start" | "center" | "end"; side? "top" | "right" | "bottom" | "left"; sideOffset? number
9
+ - DropdownMenuContent: surface? (solid | translucent | glass | glass-strong) — what the menu surface is *made of*. `solid` (default) is `bg-popover`. `translucent` matches Apple HIG / iOS menu sheets. `glass` for menus floating over rich canvases.
10
+ - DropdownMenuSubContent: surface? (solid | translucent | glass | glass-strong) — same axis applied to nested submenu surfaces
9
11
  - DropdownMenuItem: onSelect?, disabled?, asChild?, inset?
10
12
  - DropdownMenuCheckboxItem / DropdownMenuRadioItem: checked? / value, onCheckedChange? / onValueChange? (radio is on the group)
11
13
  - DropdownMenuSub / DropdownMenuSubTrigger / DropdownMenuSubContent: nested menu — sub-trigger shows children, sub-content holds the deeper items
12
14
  - DropdownMenuShortcut: children — right-aligned kbd hint
13
15
  when_to_use: A small action menu attached to a trigger — overflow "…" buttons on cards, user-avatar menus in headers, "Insert" menus in editors. For a full searchable list, use Command. For ONE primary action plus a secondary, use a Button next to a smaller ghost Button instead of a dropdown.
14
16
  composes_with: [Button (as trigger asChild), Avatar (user menu), Card (overflow on a tile), Tooltip (on the trigger)]
15
- aliases: [dropdown, dropdown menu, overflow menu, kebab menu, more menu, action menu, context-style menu, menu, pull-down menu, pulldown menu, context menu, popup menu, actions menu]
17
+ aliases: [dropdown, dropdown menu, overflow menu, kebab menu, more menu, action menu, context-style menu, menu, pull-down menu, pulldown menu, context menu, popup menu, actions menu, glass menu, frosted menu, ios menu, hig menu]
16
18
  ---
17
19
 
20
+ DropdownMenuContent sits at elevation-4. Pick the material from the scenarios below — the `surface` prop is the discoverable lever.
21
+
22
+ ---
23
+
24
+ ### Scenario 1 — Overflow menu on a row/card (default opaque)
25
+
26
+ The canonical "…" menu attached to a row or card. The content behind is a list — readability of the menu items matters more than seeing what's underneath.
27
+
18
28
  ```jsx
19
- // Overflow menu on a card/row — trigger an icon-only Button.
20
29
  <DropdownMenu>
21
30
  <DropdownMenuTrigger asChild>
22
31
  <Button variant="ghost" size="icon" aria-label="Open menu">
@@ -26,6 +35,7 @@ aliases: [dropdown, dropdown menu, overflow menu, kebab menu, more menu, action
26
35
  <DropdownMenuContent align="end">
27
36
  <DropdownMenuItem onSelect={onDuplicate}>
28
37
  <Copy /> Duplicate
38
+ <DropdownMenuShortcut>⌘D</DropdownMenuShortcut>
29
39
  </DropdownMenuItem>
30
40
  <DropdownMenuItem onSelect={onShare}>
31
41
  <Share2 /> Share
@@ -38,8 +48,87 @@ aliases: [dropdown, dropdown menu, overflow menu, kebab menu, more menu, action
38
48
  </DropdownMenu>
39
49
  ```
40
50
 
41
- DropdownMenuContent ships at elevation-4. For frosted overlays on rich canvases, opt into glass:
51
+ `solid` is the right default. Menu items are read-targets give them a clean opaque background.
52
+
53
+ ---
54
+
55
+ ### Scenario 2 — Translucent menu (iOS / Apple HIG)
56
+
57
+ You want the iOS-native menu feel: light translucency that picks up the colour of whatever's beneath without committing to a full blur. The Apple HIG canonical material for context menus.
58
+
59
+ ```jsx
60
+ <DropdownMenu>
61
+ <DropdownMenuTrigger asChild>
62
+ <Button variant="ghost" size="icon"><MoreVertical /></Button>
63
+ </DropdownMenuTrigger>
64
+ <DropdownMenuContent
65
+ surface="translucent"
66
+ className="shadow-elevation-4"
67
+ align="end"
68
+ >
69
+ <DropdownMenuLabel>Sort by</DropdownMenuLabel>
70
+ <DropdownMenuRadioGroup value={sort} onValueChange={setSort}>
71
+ <DropdownMenuRadioItem value="recent">Most recent</DropdownMenuRadioItem>
72
+ <DropdownMenuRadioItem value="alpha">A–Z</DropdownMenuRadioItem>
73
+ <DropdownMenuRadioItem value="size">Size</DropdownMenuRadioItem>
74
+ </DropdownMenuRadioGroup>
75
+ </DropdownMenuContent>
76
+ </DropdownMenu>
77
+ ```
78
+
79
+ 82% opacity. The background tints the menu without demanding the user filter it out.
80
+
81
+ ---
82
+
83
+ ### Scenario 3 — Glass menu in a canvas tool
84
+
85
+ Studio's layer-context menu, an image editor's right-click, a slide-tool insert menu. The canvas behind is the work. Glass lets the menu float without cutting a hole through the work.
86
+
87
+ ```jsx
88
+ <DropdownMenu>
89
+ <DropdownMenuTrigger asChild>
90
+ <Button variant="ghost" size="icon"><Plus /></Button>
91
+ </DropdownMenuTrigger>
92
+ <DropdownMenuContent
93
+ surface="glass"
94
+ className="shadow-elevation-4 w-56"
95
+ align="start"
96
+ >
97
+ <DropdownMenuLabel>Insert</DropdownMenuLabel>
98
+ <DropdownMenuItem><LayoutTemplate /> Layout</DropdownMenuItem>
99
+ <DropdownMenuItem><Image /> Media</DropdownMenuItem>
100
+ <DropdownMenuItem><Code2 /> Code block</DropdownMenuItem>
101
+ <DropdownMenuSeparator />
102
+ <DropdownMenuSub>
103
+ <DropdownMenuSubTrigger><Sparkles /> AI suggestion</DropdownMenuSubTrigger>
104
+ <DropdownMenuSubContent surface="glass" className="shadow-elevation-4">
105
+ <DropdownMenuItem>Layout variant</DropdownMenuItem>
106
+ <DropdownMenuItem>Tone shift</DropdownMenuItem>
107
+ <DropdownMenuItem>Density pass</DropdownMenuItem>
108
+ </DropdownMenuSubContent>
109
+ </DropdownMenuSub>
110
+ </DropdownMenuContent>
111
+ </DropdownMenu>
112
+ ```
113
+
114
+ Pass `surface="glass"` to BOTH the root content AND the sub-content — submenus default to `solid` so a glass parent with an opaque child looks broken. Match the surface consistently down the menu tree.
115
+
116
+ ---
117
+
118
+ ### Anti-patterns
119
+
120
+ **DO NOT roll glass by hand on DropdownMenuContent.**
42
121
 
43
122
  ```jsx
44
- <DropdownMenuContent className="gds-surface-glass">…</DropdownMenuContent>
123
+ {/* ❌ Misses the iOS-native edge highlight + theme blur tuning. */}
124
+ <DropdownMenuContent className="bg-popover/55 backdrop-blur-md">
125
+
126
+ {/* ✅ */}
127
+ <DropdownMenuContent surface="glass">
45
128
  ```
129
+
130
+ **DO NOT mix surfaces between content and sub-content.** A glass root with a solid submenu (or vice-versa) reads as two materials competing for attention. Pick one for the whole tree.
131
+
132
+ **DO NOT use DropdownMenu for searchable lists.** Past ~7 items the menu becomes a scrollable list and the right primitive is Command (a search-first list inside a Popover or Dialog).
133
+
134
+ **DO NOT put long-form text in menu items.** Items are action labels — verbs. If you need help text, that's a Popover surface, not a menu.
@@ -6,13 +6,21 @@ props:
6
6
  - HoverCard: open?, defaultOpen?, onOpenChange?, openDelay? (default 700), closeDelay? (default 300)
7
7
  - HoverCardTrigger: asChild?: boolean — usually a Link or Button
8
8
  - HoverCardContent: side?, align?, sideOffset?, alignOffset?, className?
9
- when_to_use: Rich preview content surfaced on hover user profile mini-cards on @-mentions, link previews, definition popups. Pointer-only by design (no touch-friendly trigger); pair with a click target for touch devices, or fall back to Popover. NEVER use HoverCard for critical info — if the user can't reach it via keyboard or touch, it might as well not exist for accessibility.
10
- composes_with: [Avatar (user preview), Card (richer content), Link (the trigger)]
11
- aliases: [hover card, hover preview, mention preview, profile peek, link preview, rich tooltip, link preview card, profile hover, peek card]
9
+ - HoverCardContent: surface? (solid | translucent | glass | glass-strong) what the preview surface is *made of*. `solid` (default) is `bg-popover`. `glass` for hover previews over rich content (a media feed, a layout canvas).
10
+ when_to_use: Rich preview content surfaced on hover — user profile mini-cards on @-mentions, link previews, definition popups, layer-thumbnail peeks. Pointer-only by design (no touch-friendly trigger); pair with a click target for touch devices, or fall back to Popover. NEVER use HoverCard for critical info — if the user can't reach it via keyboard or touch, it might as well not exist for accessibility.
11
+ composes_with: [Avatar (user preview), Card (richer content), Link (the trigger), MediaSurface (link/layer previews), Code (snippet previews)]
12
+ aliases: [hover card, hover preview, mention preview, profile peek, link preview, rich tooltip, link preview card, profile hover, peek card, glass preview, frosted preview]
12
13
  ---
13
14
 
15
+ HoverCardContent sits at elevation-4. The surface choice depends entirely on what's behind the trigger.
16
+
17
+ ---
18
+
19
+ ### Scenario 1 — User mention preview (default opaque)
20
+
21
+ The trigger is inline text in a comment thread, document, or feed. The reader's eye is on the prose; the hover-card needs to feel like a small contained card popping up next to the link. Opaque is correct.
22
+
14
23
  ```jsx
15
- // User mention preview — pointer-only enrichment.
16
24
  <HoverCard>
17
25
  <HoverCardTrigger asChild>
18
26
  <a href="/u/elena" className="font-medium underline">@elena</a>
@@ -28,8 +36,94 @@ aliases: [hover card, hover preview, mention preview, profile peek, link preview
28
36
  <span className="text-sm text-muted-foreground">
29
37
  Design lead · Joined Mar 2025
30
38
  </span>
39
+ <span className="text-sm">Currently focused on the layout-quality skill suite.</span>
31
40
  </Stack>
32
41
  </Row>
33
42
  </HoverCardContent>
34
43
  </HoverCard>
35
44
  ```
45
+
46
+ ---
47
+
48
+ ### Scenario 2 — Glass layer preview in a canvas tool
49
+
50
+ You're hovering a layer name in the Studio layer list. The canvas alongside shows the actual layer in context. A glass hover-card carrying a thumbnail of the layer keeps the canvas visible AND gives the preview presence.
51
+
52
+ ```jsx
53
+ <HoverCard openDelay={300}>
54
+ <HoverCardTrigger asChild>
55
+ <button className="text-sm hover:underline">Hero card · v0</button>
56
+ </HoverCardTrigger>
57
+ <HoverCardContent
58
+ surface="glass"
59
+ className="w-80 shadow-elevation-4"
60
+ side="right"
61
+ align="start"
62
+ >
63
+ <Stack gap="sm">
64
+ <MediaSurface
65
+ aspect="video"
66
+ source={{ kind: "image", src: "/previews/hero-v0.png" }}
67
+ alt="Hero card v0 thumbnail"
68
+ />
69
+ <Stack gap="xs">
70
+ <span className="text-sm font-medium">Hero card · v0</span>
71
+ <span className="text-xs text-muted-foreground">Last edited 2m ago by Elena</span>
72
+ </Stack>
73
+ </Stack>
74
+ </HoverCardContent>
75
+ </HoverCard>
76
+ ```
77
+
78
+ Tighter `openDelay` (300ms vs the default 700) because the user is scanning a list — they want previews to come up faster.
79
+
80
+ ---
81
+
82
+ ### Scenario 3 — Code snippet preview (translucent)
83
+
84
+ You're showing a hover preview of a code reference (a function name in docs, a symbol in a comment). Translucent lets the page peek through without committing to glass blur — feels lighter for a quick read.
85
+
86
+ ```jsx
87
+ <HoverCard>
88
+ <HoverCardTrigger asChild>
89
+ <code className="font-mono text-sm rounded bg-muted px-1.5 py-0.5">surfaceBg()</code>
90
+ </HoverCardTrigger>
91
+ <HoverCardContent
92
+ surface="translucent"
93
+ className="w-96 shadow-elevation-4 p-0"
94
+ >
95
+ <Stack gap="xs" className="p-4 pb-2">
96
+ <span className="text-sm font-medium">surfaceBg(surface, defaultBgClass)</span>
97
+ <span className="text-xs text-muted-foreground">@gradeui/ui · lib/surface</span>
98
+ </Stack>
99
+ <Code
100
+ source={`function surfaceBg(surface, defaultBgClass) {
101
+ return surface === "solid" ? defaultBgClass : "";
102
+ }`}
103
+ language="ts"
104
+ bare
105
+ className="text-xs p-4"
106
+ />
107
+ </HoverCardContent>
108
+ </HoverCard>
109
+ ```
110
+
111
+ ---
112
+
113
+ ### Anti-patterns
114
+
115
+ **DO NOT use HoverCard on touch devices for critical info.** There's no hover on touch — the preview is unreachable. Either provide a click fallback or use Popover.
116
+
117
+ **DO NOT roll glass by hand on HoverCardContent.**
118
+
119
+ ```jsx
120
+ {/* ❌ */}
121
+ <HoverCardContent className="bg-popover/60 backdrop-blur-md">
122
+
123
+ {/* ✅ */}
124
+ <HoverCardContent surface="glass">
125
+ ```
126
+
127
+ **DO NOT use HoverCard for tooltips.** Tooltips are tiny, label-only, and dismiss instantly. HoverCard is for rich content with delay. If the content is a few words, reach for Tooltip.
128
+
129
+ **DO NOT use HoverCard as a primary navigation surface.** It dismisses on pointer-out — if the user has to traverse a path to reach a button inside, the preview will close before they get there.
@@ -6,18 +6,26 @@ props:
6
6
  - Popover: open?, defaultOpen?, onOpenChange?, modal? (default false)
7
7
  - PopoverTrigger: asChild?: boolean — usually a Button
8
8
  - PopoverContent: side? "top" | "right" | "bottom" | "left"; align? "start" | "center" | "end"; sideOffset?, alignOffset?, collisionPadding?, className?
9
+ - PopoverContent: surface? (solid | translucent | glass | glass-strong) — what the popover surface is *made of*. `solid` is the default opaque `bg-popover`. `translucent` is the Apple HIG menu-sheet feel. `glass` for floating panels over rich canvases (Studio inspector, image-tool palette).
9
10
  - PopoverAnchor: asChild?: boolean — pin the popover to a different element than the trigger
10
11
  when_to_use: A floating panel anchored to a trigger that contains interactive content — date pickers, color pickers, filter pickers, "more info" panels, inline forms. Differs from Tooltip (hover-only, no focusable content) and Dialog (modal, blocks the page). DatePicker, DateRangePicker, and the Combobox pattern all compose Popover internally.
11
- composes_with: [Button (as trigger), Calendar (date picker), Command (combobox), Form controls (inline edit popover)]
12
- aliases: [popover, dropdown panel, floating panel, inline editor, attached panel, filter pop, popover view, popoverpresentation, attached popover]
12
+ composes_with: [Button (as trigger), Calendar (date picker), Command (combobox), Form controls (inline edit popover), Code (code-detail popovers)]
13
+ aliases: [popover, dropdown panel, floating panel, inline editor, attached panel, filter pop, popover view, popoverpresentation, attached popover, glass popover, frosted popover, inspector popover]
13
14
  ---
14
15
 
16
+ PopoverContent sits at elevation-4. Three scenario recipes — match the material to the canvas the popover floats over.
17
+
18
+ ---
19
+
20
+ ### Scenario 1 — Filter popover (default opaque)
21
+
22
+ You're attaching a filter picker to a button in a list/table header. The page behind is mostly white space and a table — there's nothing visually important to preserve through the popover. Opaque is the right default.
23
+
15
24
  ```jsx
16
- // Filter popover anchored to a Button trigger.
17
25
  <Popover>
18
26
  <PopoverTrigger asChild>
19
27
  <Button variant="outline" size="sm">
20
- <Filter /> Filters
28
+ <Filter className="h-4 w-4" /> Filters
21
29
  </Button>
22
30
  </PopoverTrigger>
23
31
  <PopoverContent className="w-72" align="end">
@@ -30,14 +38,118 @@ aliases: [popover, dropdown panel, floating panel, inline editor, attached panel
30
38
  <Label>Status</Label>
31
39
  <Select>{/* … */}</Select>
32
40
  </Stack>
41
+ <Row justify="end" gap="xs">
42
+ <Button variant="ghost" size="sm">Clear</Button>
43
+ <Button size="sm">Apply</Button>
44
+ </Row>
33
45
  </Stack>
34
46
  </PopoverContent>
35
47
  </Popover>
36
48
  ```
37
49
 
38
- PopoverContent ships at elevation-4. Opt into glass for a frosted look, or layer in aura when the popover is AI-driven:
50
+ `solid` keeps the form fields maximally legible. Filter popovers are read-heavy; legibility wins over aesthetic.
51
+
52
+ ---
53
+
54
+ ### Scenario 2 — Glass inspector popover (creative tool aesthetic)
55
+
56
+ You're building Studio, a presentation editor, or a vector tool. The user clicked a selected layer and a popover offers per-element knobs. The canvas behind is the work — they need to keep spatial awareness of what they just clicked. Glass is the canonical signal.
39
57
 
40
58
  ```jsx
41
- <PopoverContent className="gds-surface-glass">…</PopoverContent>
42
- <PopoverContent className="gds-aura-ring">Studio suggestion</PopoverContent>
59
+ <Popover>
60
+ <PopoverTrigger asChild>
61
+ <Button variant="ghost" size="icon"><Palette className="h-4 w-4" /></Button>
62
+ </PopoverTrigger>
63
+ <PopoverContent
64
+ surface="glass"
65
+ className="w-80 shadow-elevation-4"
66
+ align="end"
67
+ sideOffset={8}
68
+ >
69
+ <Stack gap="md">
70
+ <Row justify="between" align="center">
71
+ <span className="text-sm font-medium">Button — selected</span>
72
+ <Badge variant="outline">raised</Badge>
73
+ </Row>
74
+
75
+ <Stack gap="xs">
76
+ <Label>Tone</Label>
77
+ <Row gap="xs">
78
+ <Button size="sm" variant="raised" style={{ "--btn-glow": "var(--selected-glow)" }} />
79
+ <Button size="sm" variant="raised" style={{ "--btn-glow": "var(--success)" }} />
80
+ <Button size="sm" variant="raised" style={{ "--btn-glow": "var(--warning)" }} />
81
+ <Button size="sm" variant="raised" style={{ "--btn-glow": "var(--destructive)" }} />
82
+ </Row>
83
+ </Stack>
84
+
85
+ <Stack gap="xs">
86
+ <Label>Size</Label>
87
+ <ToggleGroup type="single" defaultValue="md">
88
+ <ToggleGroupItem value="sm">sm</ToggleGroupItem>
89
+ <ToggleGroupItem value="md">md</ToggleGroupItem>
90
+ <ToggleGroupItem value="lg">lg</ToggleGroupItem>
91
+ </ToggleGroup>
92
+ </Stack>
93
+ </Stack>
94
+ </PopoverContent>
95
+ </Popover>
43
96
  ```
97
+
98
+ `surface="glass"` + `shadow-elevation-4` is the Studio-inspector signature. The user's eye stays on the canvas; the popover reads as chrome layered above it.
99
+
100
+ ---
101
+
102
+ ### Scenario 3 — AI suggestion popover (translucent + aura)
103
+
104
+ A different shape from the destructive Dialog confirmation: an inline AI suggestion that surfaces while the user keeps working. Translucent stays light; aura announces the AI origin.
105
+
106
+ ```jsx
107
+ <Popover open={hasSuggestion}>
108
+ <PopoverAnchor>
109
+ <Code source={selectedSnippet} language="tsx" highlight={[3]} bare />
110
+ </PopoverAnchor>
111
+ <PopoverContent
112
+ surface="translucent"
113
+ className="w-96 shadow-elevation-4 gds-aura-ring"
114
+ style={{ "--aura-color": "var(--selected-glow)" }}
115
+ side="bottom"
116
+ align="start"
117
+ >
118
+ <Stack gap="sm">
119
+ <Row gap="xs" align="center">
120
+ <Sparkles className="h-4 w-4" />
121
+ <span className="text-sm font-medium">Studio suggestion</span>
122
+ </Row>
123
+ <p className="text-sm">
124
+ This Toolbar would line up edge-to-edge with the TabsList below if it used <code>size="sm"</code>. Apply?
125
+ </p>
126
+ <Row justify="end" gap="xs">
127
+ <Button variant="ghost" size="sm">Dismiss</Button>
128
+ <Button size="sm">Apply</Button>
129
+ </Row>
130
+ </Stack>
131
+ </PopoverContent>
132
+ </Popover>
133
+ ```
134
+
135
+ Note `PopoverAnchor` — the popover is pinned to the selected snippet, not to a trigger button. This is the "annotation surfaces next to the thing it annotates" pattern.
136
+
137
+ ---
138
+
139
+ ### Anti-patterns
140
+
141
+ **DO NOT roll glass by hand on PopoverContent.**
142
+
143
+ ```jsx
144
+ {/* ❌ Misses edge highlight, fixed-step blur. */}
145
+ <PopoverContent className="bg-popover/50 backdrop-blur-md">
146
+
147
+ {/* ✅ */}
148
+ <PopoverContent surface="glass">
149
+ ```
150
+
151
+ **DO NOT use Popover for content that needs a modal interaction.** Popover is non-modal — pointer-down outside dismisses it. If the user must decide before continuing, reach for Dialog.
152
+
153
+ **DO NOT use `surface="glass-strong"` on PopoverContent.** It's tuned for full-page overlays; on a 288px popover it just reads as washed out.
154
+
155
+ **DO NOT use Popover when the trigger is a hover target with no focusable content.** That's Tooltip's job — Popover requires focus, Tooltip dismisses on hover-out.
@@ -0,0 +1,153 @@
1
+ ---
2
+ name: SectionBlock
3
+ import: "@gradeui/ui"
4
+ props:
5
+ - padding? (none | sm | md | lg | xl) — vertical rhythm. Defaults to `lg`.
6
+ - background? (transparent | muted | card | primary | gradient) — tonal direction of the section bg.
7
+ - surface? (solid | translucent | glass | glass-strong) — what the section is *made of*. Orthogonal to `background`. Use `glass` for hero sections that float over a generative backdrop / image / dot grid.
8
+ - container? (default | wide | narrow | full) — max-width of the inner content.
9
+ - alignment? (left | center | right) — header / CTA alignment.
10
+ - titleSize? (sm | md | lg | xl)
11
+ - title?: string
12
+ - subtitle?: string
13
+ - cta1? / cta2? — string or `{ text, variant, href, onClick }` config
14
+ - backgroundImage?: string — direct CSS background image url
15
+ - as? "section" | "div" | "article" — semantic root
16
+ - fullBleed?: boolean
17
+ when_to_use: The top-level container for a marketing page section — hero, feature row, pricing table, testimonial strip, FAQ section. Always reach for SectionBlock over a hand-rolled `<section>` so vertical rhythm, container width, and tonal background stay consistent across the page. Pair `background="gradient"` + `surface="glass"` inner Cards for the "modern marketing hero" pattern.
18
+ composes_with: [Card (the most common child — especially with surface="glass"), Grid (feature rows), Stack (hero column), MediaSurface (hero imagery), Code (developer hero), Carousel (logo strips)]
19
+ aliases: [section, section block, hero section, marketing section, page section, content section, container section, feature section, hero, page hero, marketing hero, glass section, gradient section, mesh hero]
20
+ ---
21
+
22
+ SectionBlock is the **container axis** of a marketing page; Card is the **content axis** inside it. Three Presence axes still apply to SectionBlock: `background` (tonal direction), `surface` (material), `padding` (depth of vertical rhythm).
23
+
24
+ ---
25
+
26
+ ### Scenario 1 — Standard feature row (default)
27
+
28
+ You're laying out a feature section on a marketing page — a row of cards explaining capabilities. Calm tonal background, generous padding, default container width.
29
+
30
+ ```jsx
31
+ <SectionBlock
32
+ padding="lg"
33
+ background="muted"
34
+ title="Built for production"
35
+ subtitle="The hard primitives every team eventually needs."
36
+ alignment="center"
37
+ >
38
+ <Grid cols="3" gap="md">
39
+ <Card>
40
+ <CardHeader>
41
+ <Database className="h-5 w-5" />
42
+ <CardTitle>Data tables</CardTitle>
43
+ <CardDescription>Sorting, filtering, virtualisation.</CardDescription>
44
+ </CardHeader>
45
+ </Card>
46
+ <Card>
47
+ <CardHeader>
48
+ <Map className="h-5 w-5" />
49
+ <CardTitle>Maps</CardTitle>
50
+ <CardDescription>MapLibre default. Mapbox + Google adapters.</CardDescription>
51
+ </CardHeader>
52
+ </Card>
53
+ <Card>
54
+ <CardHeader>
55
+ <MoveVertical className="h-5 w-5" />
56
+ <CardTitle>Drag and drop</CardTitle>
57
+ <CardDescription>dnd-kit underneath, themed against tokens.</CardDescription>
58
+ </CardHeader>
59
+ </Card>
60
+ </Grid>
61
+ </SectionBlock>
62
+ ```
63
+
64
+ No `surface` prop. The default `solid` is the right answer for in-flow feature rows — the muted background sets the section apart from neighbouring sections cleanly.
65
+
66
+ ---
67
+
68
+ ### Scenario 2 — Gradient hero with glass cards (modern marketing pattern)
69
+
70
+ The canonical "shadcn-killer marketing hero" pattern. SectionBlock supplies the gradient mesh; Card children opt into glass; the two compose without either having to know about the other.
71
+
72
+ ```jsx
73
+ <SectionBlock
74
+ padding="xl"
75
+ background="gradient"
76
+ alignment="center"
77
+ title="Open the markup. Tell me which one you would merge."
78
+ subtitle="GradeUI produces code you would actually integrate."
79
+ cta1={{ text: "Open Studio", href: "/studio" }}
80
+ cta2={{ text: "Install the library", variant: "outline" }}
81
+ >
82
+ <Grid cols="2" gap="md" className="mt-8">
83
+ <Card surface="glass" className="shadow-elevation-4">
84
+ <CardHeader>
85
+ <CardTitle>v0 — sidebar component</CardTitle>
86
+ <CardDescription>~300 lines</CardDescription>
87
+ </CardHeader>
88
+ <CardContent className="p-0">
89
+ <Code source={v0Code} language="tsx" bare className="p-4 text-xs max-h-72" />
90
+ </CardContent>
91
+ </Card>
92
+
93
+ <Card surface="glass" className="shadow-elevation-4 gds-aura-ring">
94
+ <CardHeader>
95
+ <CardTitle>GradeUI — sidebar component</CardTitle>
96
+ <CardDescription>6 lines</CardDescription>
97
+ </CardHeader>
98
+ <CardContent className="p-0">
99
+ <Code source={gradeCode} language="tsx" bare className="p-4 text-xs max-h-72" />
100
+ </CardContent>
101
+ </Card>
102
+ </Grid>
103
+ </SectionBlock>
104
+ ```
105
+
106
+ This is the pattern the home-diff-hero scaffold uses. `background="gradient"` paints the mesh; the Cards float through it via `surface="glass"`; `gds-aura-ring` on the second card draws the eye to the recommended path. No Tailwind soup anywhere.
107
+
108
+ ---
109
+
110
+ ### Scenario 3 — Glass section over a backgroundImage (image hero)
111
+
112
+ You're using a hero image as the section background. A solid section panel over it would defeat the image. A glass section keeps the image visible while focusing the eye on the content overlay.
113
+
114
+ ```jsx
115
+ <SectionBlock
116
+ padding="xl"
117
+ surface="glass"
118
+ backgroundImage="/hero/teams-shipping.jpg"
119
+ alignment="center"
120
+ title="For teams shipping software"
121
+ subtitle="The primitive layer modern product teams actually use."
122
+ cta1={{ text: "Open Studio" }}
123
+ container="narrow"
124
+ >
125
+ <Row justify="center" gap="lg" className="text-sm text-muted-foreground">
126
+ <span>Linear</span><span>Vercel</span><span>Stripe</span><span>Anthropic</span>
127
+ </Row>
128
+ </SectionBlock>
129
+ ```
130
+
131
+ `background` stays at the default `transparent` so the image shows through; `surface="glass"` paints the frosted overlay on top with edge highlight + theme-tuned blur. The narrow container caps content width so the hero stays readable over the image.
132
+
133
+ ---
134
+
135
+ ### Anti-patterns
136
+
137
+ **DO NOT roll glass by hand at the section level.**
138
+
139
+ ```jsx
140
+ {/* ❌ */}
141
+ <section className="py-20 bg-card/40 backdrop-blur-md">
142
+
143
+ {/* ✅ */}
144
+ <SectionBlock surface="glass" padding="xl">
145
+ ```
146
+
147
+ **DO NOT use `background="primary"` + `surface="glass"`.** The primary fill is intentionally opaque (it's a brand statement). Layering glass on top makes the brand colour read as washed-out. Pick one signal.
148
+
149
+ **DO NOT skip SectionBlock for marketing rows.** Hand-rolling `<section className="py-20">` means every section gets a slightly different vertical rhythm and container width — the page reads as drift. SectionBlock is the rhythm primitive.
150
+
151
+ **DO NOT use `padding="xl"` for in-app sections.** xl padding is marketing-page territory. In-app section breaks should use `sm` or `md` — anything more and your dashboard reads as a marketing page.
152
+
153
+ **DO NOT use `surface="glass-strong"` on SectionBlock unless the section is acting as a full-page overlay.** It's tuned for very heavy de-emphasis of what's underneath; on a regular section it just looks washed-out.