Design · 4 min
Components
Fifteen blocks to copy, a handful of components that hold behaviour, and no configurable library.
Two kinds of thing live in src/components. Blocks are whole page
sections you copy and edit. Components are the few small pieces that hold
behaviour rather than styles, and those you import.
Neither is a configurable library. There is no variant prop and no theme
object, because the moment a block takes six props to look right, editing the
file would have been faster.
The blocks
Fifteen of them, in src/components/blocks. Open /blocks in your own app to
see every one rendered with sample content, find the closest, copy the file
into your page and change it. They read from the same design tokens as
everything else, so they arrive in your colours.
| Block | For |
|---|---|
Hero | The first screen: eyebrow, title, two actions |
LogoCloud | The row of names under the hero |
FeatureGrid | Three to six things your product does |
Steps | A numbered how-it-works |
Stats | Four numbers worth stating |
Pricing | Plan cards, one highlighted |
Faq | Native <details>, so it works before hydration |
Testimonials | Quotes with an attribution |
Cta | The closing ask |
Footer | Link columns and the small print |
Banner | A dismissible strip, remembered in local storage |
EmptyState | The first screen of a table with no rows |
DataTable | A table on desktop, cards on a phone |
SettingsForm | A form with per-field errors and a pending state |
Pagination | Previous and next, as links, so a page is a URL |
The components
Eleven of them, and most exist because they hold behaviour, not because they hold styles.
The two you will reuse
ui/Card is the panel shape: border on a raised surface, consistent
padding, optional title and lead. Everything in the dashboard is one.
<Card title="Billing" lead="Your purchases and the keys they issued.">
<Receipts rows={orders} />
</Card>ui/Field is a labelled input with its hint and error in the right place
and the label actually associated with the control, which is the part people
skip.
<Field label="GitHub username" note="Invitations go to this account.">
<input name="github_login" />
</Field>The ones that hold behaviour
| Component | What it knows |
|---|---|
SignInForm | The magic link round trip and its three states |
SignOutButton | Refresh before push, so the server sees the cleared cookie |
LocaleSwitcher | Writes the cookie the server reads on the next render |
CopyField | Clipboard, plus the confirmation the click is meant to give |
Logo, Icons | Line-work SVG, sized in em so they follow the text |
Why not more
A boilerplate that ships two hundred components ships two hundred opinions you have to override. The ones here are the ones that would otherwise be copy-pasted with a bug in the third copy.
When you need a real primitive, a dialog with a focus trap, a combobox, a date picker, install Radix or Base UI and style it with your tokens. Do not write your own dialog. Accessible focus management is genuinely hard and entirely solved.
Adding one
Server component by default. Add "use client" only when you need state, an
effect or an event handler, and push it as far down the tree as it will go: a
client component makes everything it renders client too.
Something wrong or missing on this page? Tell us.