FAQ accordion
Native details and summary elements — no JavaScript at all.
MarketingstarterFeaturedfaqaccordiondetailsprogressive-enhancement
Live preview
full widthLive preview — open it in a new tab for the full-height version.
Source
This exact file renders the preview above.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { Container } from '@/components/ui/layout'
import { faqs } from '@/content/demo'
/**
* FAQ accordion
*
* Built on native `<details>` and `<summary>`. The native element gives
* expand/collapse, keyboard operation and screen-reader announcement with no
* JavaScript at all — which means this section still works before hydration,
* and on a page that never hydrates.
*/
export default function AccordionFAQ() {
return (
<section className="border-b border-line bg-canvas py-section">
<Container size="narrow">
<div className="max-w-2xl">
<h2 className="display-type text-2xl font-semibold text-ink-strong sm:text-3xl">
Questions people actually ask.
</h2>
</div>
<div className="mt-10 divide-y divide-[var(--color-border)] border-y border-line">
{faqs.map((faq) => (
<details key={faq.question} className="group py-1">
<summary className="flex cursor-pointer list-none items-center justify-between gap-4 py-4 text-left text-md font-medium text-ink-strong marker:content-none [&::-webkit-details-marker]:hidden">
{faq.question}
<span
className="relative size-4 shrink-0 text-ink-subtle transition-transform duration-200 group-open:rotate-45"
aria-hidden="true"
>
<span className="absolute top-1/2 left-0 h-px w-4 -translate-y-1/2 bg-current" />
<span className="absolute top-0 left-1/2 h-4 w-px -translate-x-1/2 bg-current" />
</span>
</summary>
<p className="pb-5 text-sm leading-relaxed text-ink-muted">{faq.answer}</p>
</details>
))}
</div>
</Container>
</section>
)
}
components/blocks/sections/faq/accordion.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { Container } from '@/components/ui/layout'
import { faqs } from '@/content/demo'
/**
* FAQ accordion
*
* Built on native `<details>` and `<summary>`. The native element gives
* expand/collapse, keyboard operation and screen-reader announcement with no
* JavaScript at all — which means this section still works before hydration,
* and on a page that never hydrates.
*/
export default function AccordionFAQ() {
return (
<section className="border-b border-line bg-canvas py-section">
<Container size="narrow">
<div className="max-w-2xl">
<h2 className="display-type text-2xl font-semibold text-ink-strong sm:text-3xl">
Questions people actually ask.
</h2>
</div>
<div className="mt-10 divide-y divide-[var(--color-border)] border-y border-line">
{faqs.map((faq) => (
<details key={faq.question} className="group py-1">
<summary className="flex cursor-pointer list-none items-center justify-between gap-4 py-4 text-left text-md font-medium text-ink-strong marker:content-none [&::-webkit-details-marker]:hidden">
{faq.question}
<span
className="relative size-4 shrink-0 text-ink-subtle transition-transform duration-200 group-open:rotate-45"
aria-hidden="true"
>
<span className="absolute top-1/2 left-0 h-px w-4 -translate-y-1/2 bg-current" />
<span className="absolute top-0 left-1/2 h-4 w-px -translate-x-1/2 bg-current" />
</span>
</summary>
<p className="pb-5 text-sm leading-relaxed text-ink-muted">{faq.answer}</p>
</details>
))}
</div>
</Container>
</section>
)
}
content/demo.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
/**
* Shared demo content.
*
* Marketing copy lives here rather than inside JSX, so a section file stays a
* layout decision and nothing else. Every section in the library draws from
* this module, which is also what keeps 110 sections from becoming 110 copies
* of the same paragraph.
*
* All names, companies and figures are fictional.
*/
export interface Feature {
title: string
description: string
/** lucide-react icon name, resolved by the consuming section. */
icon: string
}
export const features: Feature[] = [
{
title: 'Token-driven theming',
description:
'Three orthogonal axes — scheme, palette, density — compose into every surface. Change one and the whole system follows.',
icon: 'Palette',
},
{
title: 'Accessible by construction',
description:
'Focus management, live regions and keyboard contracts are built into the primitives, not bolted on during review.',
icon: 'Accessibility',
},
{
title: 'Server-first rendering',
description:
'Sections are Server Components. Interactivity is scoped to small client islands, so a marketing page ships almost no JavaScript.',
icon: 'Server',
},
{
title: 'Composable sections',
description:
'Every block is a standalone export. Assemble a page from nine of them, or lift one into an existing codebase.',
icon: 'Layers',
},
{
title: 'Real source, always',
description:
'The code viewer reads the same file that renders the preview. Documentation cannot drift from implementation.',
icon: 'Code2',
},
{
title: 'Built to grow',
description:
'Adding a component means adding a catalogue record and a file. No page in the application needs to change.',
icon: 'Sprout',
},
]
export const secondaryFeatures: Feature[] = [
{
title: 'Responsive previews',
description: 'Inspect any block at 390, 768 and 1440 without leaving the page.',
icon: 'Smartphone',
},
{
title: 'Deterministic copy',
description: 'The copy button copies exactly what the viewer displays — no rewriting.',
icon: 'Clipboard',
},
{
title: 'Typed catalogue',
description: 'One record shape across six families, validated by the test suite.',
icon: 'ListTree',
},
{
title: 'Zero runtime CSS',
description: 'Tailwind v4 plus custom properties. No styling library in the bundle.',
icon: 'Feather',
},
{
title: 'Keyboard everything',
description: 'Menus, tabs, dialogs and the palette all follow published ARIA patterns.',
icon: 'Keyboard',
},
{
title: 'Dark authored, not inverted',
description: 'The dark scheme is designed, not derived from a filter.',
icon: 'Moon',
},
]
export interface Stat {
value: string
label: string
detail?: string
}
export const stats: Stat[] = [
{ value: '4', label: 'Composition levels', detail: 'Primitives through complete products' },
{ value: '3', label: 'Theme axes', detail: 'Scheme, palette, density' },
{ value: '0', label: 'Runtime dependencies', detail: 'Beyond React, Next and icons' },
{ value: '100%', label: 'Statically rendered', detail: 'Every catalogue route' },
]
export const companyLogos = [
'Northwind',
'Halcyon',
'Meridian',
'Kestrel',
'Lumen Works',
'Atlas Forge',
'Verdant',
'Ironwood',
]
export interface Testimonial {
quote: string
name: string
role: string
company: string
}
export const testimonials: Testimonial[] = [
{
quote:
'We replaced four half-finished internal libraries with one. The density axis alone paid for the migration — our admin tools and our marketing site finally share components.',
name: 'Priya Raman',
role: 'Principal Engineer',
company: 'Northwind',
},
{
quote:
'The accessibility work is the part I did not expect. Focus return, live regions, manual tab activation — the things that normally get filed as tech debt were already handled.',
name: 'Tomas Lindqvist',
role: 'Head of Design Systems',
company: 'Halcyon',
},
{
quote:
'Being able to read the exact source that renders the preview removed an entire class of "the docs are wrong" tickets.',
name: 'Amara Osei',
role: 'Staff Frontend Engineer',
company: 'Meridian',
},
{
quote:
'Our first landing page took an afternoon. The second took forty minutes, because by then we were composing rather than building.',
name: 'Jun Watanabe',
role: 'Product Lead',
company: 'Kestrel',
},
{
quote:
'Five palettes sounds like a gimmick until a client asks for a different accent on the third day and it is a one-line change.',
name: 'Elena Rossi',
role: 'Creative Director',
company: 'Lumen Works',
},
{
quote:
'The starters are the honest part. They are not screenshots — every route resolves, including the 404s.',
name: 'Marcus Bell',
role: 'Engineering Manager',
company: 'Atlas Forge',
},
]
export interface PricingTier {
name: string
price: string
cadence: string
description: string
features: string[]
cta: string
featured?: boolean
note?: string
}
export const pricingTiers: PricingTier[] = [
{
name: 'Solo',
price: '$0',
cadence: 'forever',
description: 'For personal projects and evaluation.',
features: [
'Every component and section',
'Copy-paste source',
'Light and dark schemes',
'Community support',
],
cta: 'Start building',
},
{
name: 'Team',
price: '$18',
cadence: 'per seat / month',
description: 'For product teams shipping together.',
features: [
'Everything in Solo',
'All five palettes',
'Starter products',
'Figma token export',
'Priority issue triage',
],
cta: 'Start a trial',
featured: true,
note: 'Most teams start here.',
},
{
name: 'Business',
price: '$2,400',
cadence: 'per year',
description: 'For organisations with a design-system function.',
features: [
'Everything in Team',
'Unlimited seats',
'Private component registry',
'Accessibility review sessions',
'Named support contact',
],
cta: 'Contact sales',
},
]
export interface FAQ {
question: string
answer: string
}
export const faqs: FAQ[] = [
{
question: 'Is Foundry a published npm package?',
answer:
'No. Foundry is a template demonstration. Components are copied into your project and owned by you — there is no registry, no install command and no version to upgrade.',
},
{
question: 'How do I change the accent colour?',
answer:
'Every accent in the system resolves from one ramp of custom properties. Redefine those seven values in your stylesheet and every button, focus ring, link and badge follows.',
},
{
question: 'Do the components require JavaScript?',
answer:
'Most do not. Sections, cards, tables, forms and layout primitives render entirely on the server. Only overlays, menus and the command palette are client islands.',
},
{
question: 'What does the density axis actually change?',
answer:
'Control heights, control padding, stack rhythm, card padding, table row height and section padding. It deliberately does not change the type scale, so text stays legible at every density.',
},
{
question: 'Can I use only part of the library?',
answer:
'Yes. Each block is a standalone file whose imports are visible in the code viewer. Copying one section brings the primitives it names and nothing else.',
},
{
question: 'How is the source in the documentation kept accurate?',
answer:
'A build step reads the real files from disk and generates the source registry. The preview and the code you copy come from the same file, and a test fails if the two ever diverge.',
},
{
question: 'Is there a Figma library?',
answer:
'The token manifest is designed to be exported to Figma variables, but no Figma file ships with this demo.',
},
{
question: 'What browsers are supported?',
answer:
'Current versions of Chrome, Edge, Safari and Firefox. The library uses container-free responsive CSS and standard custom properties throughout.',
},
]
export interface TeamMember {
name: string
role: string
bio: string
location: string
}
export const team: TeamMember[] = [
{
name: 'Priya Raman',
role: 'Design systems',
bio: 'Spent six years untangling component libraries before deciding to write one properly.',
location: 'Bengaluru',
},
{
name: 'Tomas Lindqvist',
role: 'Accessibility',
bio: 'Audits by day, writes focus-management utilities by night.',
location: 'Stockholm',
},
{
name: 'Amara Osei',
role: 'Frontend architecture',
bio: 'Believes most performance problems are really architecture problems.',
location: 'Accra',
},
{
name: 'Jun Watanabe',
role: 'Product',
bio: 'Turns "we should document this" into things that are actually documented.',
location: 'Osaka',
},
{
name: 'Elena Rossi',
role: 'Visual design',
bio: 'Editorial background; still measures leading by eye and is usually right.',
location: 'Milan',
},
{
name: 'Marcus Bell',
role: 'Developer experience',
bio: 'Optimises for the twentieth time you use something, not the first.',
location: 'Manchester',
},
{
name: 'Sofia Delgado',
role: 'Documentation',
bio: 'Thinks a component without a usage note is only half shipped.',
location: 'Madrid',
},
{
name: 'Idris Karim',
role: 'Infrastructure',
bio: 'Keeps the build under a minute so nobody is tempted to skip it.',
location: 'Toronto',
},
]
export interface ProcessStep {
title: string
description: string
duration?: string
}
export const processSteps: ProcessStep[] = [
{
title: 'Audit',
description:
'Inventory every surface, control and one-off in the existing product. Nothing is designed before the mess is measured.',
duration: 'Week 1–2',
},
{
title: 'Tokenise',
description:
'Extract the real colour, type and spacing decisions into a token set the whole team can read.',
duration: 'Week 3',
},
{
title: 'Build primitives',
description:
'Ship the twenty components that account for eighty per cent of screens, with states and keyboard contracts.',
duration: 'Week 4–7',
},
{
title: 'Compose',
description:
'Assemble sections and page patterns, then migrate a real route to prove the system holds.',
duration: 'Week 8–10',
},
{
title: 'Document',
description:
'Write the usage guidance while the decisions are fresh, and wire the docs to the source.',
duration: 'Week 11',
},
{
title: 'Hand over',
description:
'Contribution guide, review checklist, and a session on how to add the twenty-first component.',
duration: 'Week 12',
},
]
export interface TimelineEvent {
year: string
title: string
description: string
}
export const timelineEvents: TimelineEvent[] = [
{
year: '2021',
title: 'One repository, four libraries',
description:
'Four product teams, four component sets, four opinions about what "medium" meant.',
},
{
year: '2022',
title: 'The first token pass',
description: 'Colour and spacing consolidated. Everything else stayed where it was.',
},
{
year: '2023',
title: 'Primitives extracted',
description:
'Twenty components, one focus treatment, one control height. Adoption became easier than resistance.',
},
{
year: '2024',
title: 'Sections and patterns',
description: 'Marketing stopped rebuilding heroes and started composing them.',
},
{
year: '2025',
title: 'Density as an axis',
description: 'Admin tools and marketing pages finally shared a component set.',
},
{
year: '2026',
title: 'Foundry 1.0',
description: 'Four levels, one system, documented against its own source.',
},
]
export interface CaseStudy {
slug: string
client: string
title: string
summary: string
sector: string
year: string
metrics: Array<{ label: string; value: string }>
}
export const caseStudies: CaseStudy[] = [
{
slug: 'northwind-console',
client: 'Northwind',
title: 'One console for eleven internal tools',
summary:
'Eleven admin surfaces, each with its own table implementation, consolidated into a single shell with a shared data layer.',
sector: 'Logistics',
year: '2025',
metrics: [
{ label: 'Tools consolidated', value: '11' },
{ label: 'Bundle reduction', value: '61%' },
{ label: 'Time to new screen', value: '2 days' },
],
},
{
slug: 'halcyon-storefront',
client: 'Halcyon',
title: 'A storefront that loads on a train',
summary:
'Rebuilt a client-rendered catalogue as server components with small islands, targeting the worst connection the team could find.',
sector: 'Retail',
year: '2025',
metrics: [
{ label: 'JS shipped', value: '−78%' },
{ label: 'LCP, 3G', value: '1.4s' },
{ label: 'Conversion', value: '+9%' },
],
},
{
slug: 'meridian-docs',
client: 'Meridian',
title: 'Documentation that cannot go stale',
summary:
'Wired the documentation site to the component source so every example is generated from the file it documents.',
sector: 'Developer tools',
year: '2024',
metrics: [
{ label: 'Stale examples', value: '0' },
{ label: 'Docs PRs / month', value: '×3' },
{ label: 'Support tickets', value: '−34%' },
],
},
{
slug: 'kestrel-onboarding',
client: 'Kestrel',
title: 'Onboarding in four steps instead of nine',
summary:
'Reduced a nine-screen signup to four, with real validation, resumable progress and a working keyboard path throughout.',
sector: 'Fintech',
year: '2024',
metrics: [
{ label: 'Steps removed', value: '5' },
{ label: 'Completion', value: '+27%' },
{ label: 'Support contacts', value: '−41%' },
],
},
{
slug: 'lumen-rebrand',
client: 'Lumen Works',
title: 'A rebrand in one pull request',
summary:
'Because every surface resolved from tokens, a full visual rebrand touched one file and shipped in a single review.',
sector: 'Media',
year: '2023',
metrics: [
{ label: 'Files changed', value: '1' },
{ label: 'Screens updated', value: '240+' },
{ label: 'Regressions', value: '0' },
],
},
]
export interface Post {
slug: string
title: string
excerpt: string
category: string
date: string
readingTime: string
author: string
}
export const posts: Post[] = [
{
slug: 'density-as-an-axis',
title: 'Density belongs in your token system',
excerpt:
'Most design systems treat compact mode as a table prop. Making it a theme axis is what finally lets admin tools and marketing pages share components.',
category: 'Design systems',
date: '2026-03-12',
readingTime: '8 min',
author: 'Priya Raman',
},
{
slug: 'manual-tab-activation',
title: 'Why your tabs should not auto-activate',
excerpt:
'Automatic activation feels responsive and quietly punishes keyboard users. A short argument for the boring default.',
category: 'Accessibility',
date: '2026-02-27',
readingTime: '6 min',
author: 'Tomas Lindqvist',
},
{
slug: 'documentation-from-source',
title: 'Generate documentation from the file, not about it',
excerpt:
'A build step that reads the real component and a test that fails on drift removes an entire category of wrong documentation.',
category: 'Tooling',
date: '2026-02-09',
readingTime: '5 min',
author: 'Sofia Delgado',
},
{
slug: 'server-components-marketing',
title: 'Marketing pages do not need a framework runtime',
excerpt:
'What actually needs to be interactive on a landing page, and how small the remaining island can be.',
category: 'Performance',
date: '2026-01-22',
readingTime: '9 min',
author: 'Amara Osei',
},
{
slug: 'variants-that-earn-their-place',
title: 'A variant should represent a decision, not a padding value',
excerpt:
'How to tell a real variant from an inflated catalogue, and why one hundred good components beat three hundred similar ones.',
category: 'Design systems',
date: '2026-01-08',
readingTime: '7 min',
author: 'Elena Rossi',
},
{
slug: 'focus-return',
title: 'The half of focus management everyone forgets',
excerpt:
'Trapping focus in a dialog is the easy part. Putting it back where it came from is what keyboard users actually notice.',
category: 'Accessibility',
date: '2025-12-15',
readingTime: '4 min',
author: 'Tomas Lindqvist',
},
]
export interface Integration {
name: string
category: string
description: string
}
export const integrations: Integration[] = [
{
name: 'Vector',
category: 'Analytics',
description: 'Event streaming with a typed schema registry.',
},
{
name: 'Cinder',
category: 'Monitoring',
description: 'Traces, logs and alerts in one timeline.',
},
{
name: 'Postmark',
category: 'Email',
description: 'Transactional delivery with per-template metrics.',
},
{ name: 'Harbour', category: 'Storage', description: 'Object storage with signed upload URLs.' },
{
name: 'Sentinel',
category: 'Security',
description: 'Dependency and secret scanning on every push.',
},
{ name: 'Ledger', category: 'Billing', description: 'Usage metering and invoice generation.' },
{
name: 'Relay',
category: 'Messaging',
description: 'Webhooks with automatic retry and replay.',
},
{
name: 'Atlas',
category: 'Search',
description: 'Typo-tolerant search with faceted filtering.',
},
{ name: 'Beacon', category: 'Support', description: 'Shared inbox with conversation routing.' },
{ name: 'Quarry', category: 'Data', description: 'Warehouse sync with incremental models.' },
{ name: 'Pilot', category: 'Deployment', description: 'Preview environments per pull request.' },
{ name: 'Lantern', category: 'Documentation', description: 'Docs generated from typed schemas.' },
]
export interface Product {
slug: string
name: string
category: string
priceCents: number
compareAtCents?: number
description: string
material: string
badge?: string
inStock: boolean
rating: number
reviews: number
}
export const products: Product[] = [
{
slug: 'field-shell-jacket',
name: 'Field Shell Jacket',
category: 'outerwear',
priceCents: 32800,
compareAtCents: 41000,
description:
'A three-layer shell cut close enough to wear in a city and sealed well enough not to.',
material: 'Recycled ripstop',
badge: 'Last season pricing',
inStock: true,
rating: 4.7,
reviews: 214,
},
{
slug: 'quarry-overshirt',
name: 'Quarry Overshirt',
category: 'outerwear',
priceCents: 18500,
description: 'Heavyweight cotton twill that behaves like a jacket and packs like a shirt.',
material: 'Organic cotton twill',
inStock: true,
rating: 4.5,
reviews: 132,
},
{
slug: 'meridian-knit',
name: 'Meridian Merino Knit',
category: 'knitwear',
priceCents: 14900,
description: 'Fine-gauge merino with a high neck and no branding anywhere on it.',
material: 'Extrafine merino',
badge: 'Restocked',
inStock: true,
rating: 4.8,
reviews: 341,
},
{
slug: 'harbour-cardigan',
name: 'Harbour Cardigan',
category: 'knitwear',
priceCents: 21000,
description: 'A boxy cardigan with horn buttons and a shawl collar that holds its shape.',
material: 'Lambswool',
inStock: false,
rating: 4.4,
reviews: 88,
},
{
slug: 'atlas-chino',
name: 'Atlas Chino',
category: 'trousers',
priceCents: 12800,
compareAtCents: 15900,
description: 'A straight-leg chino with a proper waistband and pockets that hold a phone.',
material: 'Compact cotton',
inStock: true,
rating: 4.6,
reviews: 507,
},
{
slug: 'ironwood-trouser',
name: 'Ironwood Wool Trouser',
category: 'trousers',
priceCents: 24500,
description: 'Pleated, cuffed, and cut for people who sit down during the day.',
material: 'Wool flannel',
inStock: true,
rating: 4.3,
reviews: 76,
},
{
slug: 'lumen-tote',
name: 'Lumen Tote',
category: 'accessories',
priceCents: 9800,
description: 'Waxed canvas with a leather base and a strap long enough for a coat.',
material: 'Waxed canvas',
badge: 'Best seller',
inStock: true,
rating: 4.9,
reviews: 623,
},
{
slug: 'kestrel-cap',
name: 'Kestrel Six-Panel Cap',
category: 'accessories',
priceCents: 5200,
description: 'Unstructured, cotton-lined, with a brim that survives being sat on.',
material: 'Cotton canvas',
inStock: true,
rating: 4.2,
reviews: 195,
},
{
slug: 'verdant-tee',
name: 'Verdant Heavy Tee',
category: 'basics',
priceCents: 4800,
description: 'A 240gsm tee that holds its collar past the first wash.',
material: 'Organic cotton',
inStock: true,
rating: 4.6,
reviews: 1102,
},
{
slug: 'halcyon-socks',
name: 'Halcyon Ribbed Socks',
category: 'basics',
priceCents: 2200,
description: 'Three pairs. Ribbed, reinforced, and the same length as each other.',
material: 'Merino blend',
inStock: true,
rating: 4.5,
reviews: 289,
},
]
export const productCategories = [
{ slug: 'outerwear', name: 'Outerwear', description: 'Shells, overshirts and coats.' },
{ slug: 'knitwear', name: 'Knitwear', description: 'Merino, lambswool and cotton knits.' },
{ slug: 'trousers', name: 'Trousers', description: 'Chinos, wool and workwear cuts.' },
{ slug: 'accessories', name: 'Accessories', description: 'Bags, caps and small goods.' },
{ slug: 'basics', name: 'Basics', description: 'Tees, socks and everyday layers.' },
]
export interface MenuDish {
name: string
description: string
price: string
tags?: string[]
}
export interface MenuCourse {
course: string
note?: string
dishes: MenuDish[]
}
export const restaurantMenu: MenuCourse[] = [
{
course: 'To begin',
note: 'Served from opening until close.',
dishes: [
{
name: 'Cured trout, fennel, dill oil',
description: 'Two-day cure, shaved fennel, dill pressed the same morning.',
price: '16',
tags: ['GF'],
},
{
name: 'Charred leek, hazelnut, aged sheep cheese',
description: 'Leeks over embers until the outer layer gives up entirely.',
price: '14',
tags: ['V'],
},
{
name: 'Sourdough, cultured butter',
description: 'Four-day starter, butter churned in-house on Tuesdays.',
price: '7',
tags: ['V'],
},
],
},
{
course: 'Mains',
dishes: [
{
name: 'Aged duck breast, quince, bitter leaves',
description: 'Dry-aged fourteen days, quince from the orchard at Ashfield.',
price: '34',
},
{
name: 'Turbot on the bone, brown butter, capers',
description: 'Whole fish for two, carved at the table.',
price: '58',
tags: ['For two'],
},
{
name: 'Barley, wild mushroom, preserved lemon',
description: 'Slow-cooked barley with a mushroom broth reduced for six hours.',
price: '26',
tags: ['V', 'Vegan on request'],
},
],
},
{
course: 'To finish',
dishes: [
{
name: 'Burnt honey tart, crème fraîche',
description: 'Honey taken just past the point most people would stop.',
price: '12',
tags: ['V'],
},
{
name: 'Poached pear, walnut, blue cheese',
description: 'Pears poached in the last of the autumn cider.',
price: '13',
tags: ['V'],
},
{
name: 'Selection of British cheese',
description: 'Three cheeses, oat crackers, quince paste.',
price: '16',
tags: ['V'],
},
],
},
]
export const changelog = [
{
version: '1.0.0',
date: '2026-03-14',
title: 'Foundry 1.0',
summary:
'The first complete release: four composition levels, one token system, one catalogue.',
changes: [
{
kind: 'added' as const,
text: '45 primitives across actions, forms, feedback, overlay, navigation, data and layout.',
},
{
kind: 'added' as const,
text: '15 navigation patterns, 12 header systems and 10 footer systems.',
},
{
kind: 'added' as const,
text: '28 form flows with real validation, first-invalid focus and live error announcement.',
},
{
kind: 'added' as const,
text: '112 sections spanning marketing, commerce, SaaS, content, application and state.',
},
{
kind: 'added' as const,
text: '10 complete page patterns and 10 multi-route starter products.',
},
{
kind: 'added' as const,
text: 'Theme playground with five palettes and three densities, persisted and flash-free.',
},
],
},
{
version: '0.9.0',
date: '2026-02-20',
title: 'Starters and the composition playground',
summary: 'Level 4 arrives, and sections become assemblable rather than merely browsable.',
changes: [
{
kind: 'added' as const,
text: 'Ten starter products, each with working navigation and its own 404 path.',
},
{ kind: 'added' as const, text: 'URL-driven section composition playground.' },
{
kind: 'changed' as const,
text: 'Catalogue records gained `relatedItems`, validated to resolve.',
},
{
kind: 'fixed' as const,
text: 'Drawer no longer restored focus to a detached element after route changes.',
},
],
},
{
version: '0.8.0',
date: '2026-01-30',
title: 'Density becomes an axis',
summary: 'Compact, Default and Relaxed move out of component props and into the token layer.',
changes: [
{
kind: 'added' as const,
text: 'Density tokens for control height, padding, stack rhythm and row height.',
},
{
kind: 'changed' as const,
text: 'Every control now derives its height from `--density-control-height`.',
},
{
kind: 'removed' as const,
text: 'Per-component `compact` props, superseded by the density axis.',
},
],
},
{
version: '0.7.0',
date: '2026-01-12',
title: 'Source registry',
summary: 'Documentation is generated from the files it documents.',
changes: [
{
kind: 'added' as const,
text: 'Build-time source registry; the viewer reads the real file.',
},
{
kind: 'added' as const,
text: 'Drift test that regenerates the registry and fails on mismatch.',
},
{
kind: 'fixed' as const,
text: 'Copy button now falls back to a hidden textarea on insecure origins.',
},
],
},
{
version: '0.6.0',
date: '2025-12-04',
title: 'Accessibility pass',
summary: 'Focus management and live regions moved into the primitives.',
changes: [
{
kind: 'added' as const,
text: 'Focus trap with guaranteed focus return across Dialog, Drawer and the palette.',
},
{ kind: 'changed' as const, text: 'Tabs switched to manual activation.' },
{ kind: 'fixed' as const, text: 'Tooltips now open on keyboard focus, not only on hover.' },
{
kind: 'fixed' as const,
text: 'Toast queue announces in order instead of interrupting itself.',
},
],
},
]
export const faqShort = faqs.slice(0, 4)
Demo source — adapt to your project. Foundry is not published as a package.
Usage
The native element gives expand and collapse, keyboard operation and screen-reader announcement for free, which means this section works before hydration and on a page that never hydrates.
- Never rebuild an accordion in JavaScript unless you need multiple-open coordination.
- The indicator rotates rather than swapping glyphs, so no icon flash on toggle.
Variants and states
Every entry below is a genuine difference in behaviour or layout, and every one of them is visible in the preview above.
- Eight questions
- Plus-to-cross indicator
- Zero JavaScript
Accessibility
- Native disclosure
- details and summary carry expanded state without ARIA.
- Marker removal
- The default marker is removed in both WebKit and standard syntax.
Foundry implements published ARIA patterns and is tested against them. No WCAG certification is claimed — see the accessibility documentation for what is and is not covered.