Build
How components work
Every component follows the same pattern. One file, a variant prop, your content through props and a few built-in texts in two languages.
On this page
One file per component#
A component is a single .tsx file in src/components/ui. It exports the component, its props type and the type of its variants. Its own texts sit in the file as TEXTS, in both languages.
import { cn, useTexts } from '@/lib/kit';
import { Button } from '@/components/ui/button';
export type FaqVariant = 'accordion' | 'two-column' | 'grid' | …;
const TEXTS = {
en: { search: 'Search questions', … },
de: { search: 'Fragen durchsuchen', … },
};
export interface FaqProps {
variant?: FaqVariant;
title?: React.ReactNode;
items: FaqItem[];
labels?: Partial<(typeof TEXTS)['en']>;
className?: string;
}
export function Faq(props: FaqProps) { … }Imports always go through the alias, @/components/ui/<name> for components and @/lib/kit for helpers such as cn, KitProvider and useInView.
Variants#
One component, many layouts. The variant prop picks one, the hero knows 17, the FAQ 7. Your content stays the same, you only swap the word.
<Hero variant="centered" title="…" />
<Hero variant="split" title="…" media={<img src="/shop.avif" alt="…" />} />
<Hero variant="with-stats" title="…" stats={[{ value: '1962', label: 'founded' }]} />Every variant has a live preview on its page, for example under Hero.
Content through props#
Everything on the page comes through props. A component carries no brand and no fixed copy, it fits a law firm as well as an online shop.
import { Faq } from '@/components/ui/faq';
<Faq
variant="accordion"
title="Good to know"
defaultOpen={0}
schema
items={[
{ question: 'Can I pay in the shop?', answer: 'Yes, by card or in cash when you pick up.' },
{ question: 'How early should I order?', answer: 'By 6 pm the day before, two days ahead for cakes.' },
]}
/>schema writes the structured data for search engines as well. The main props of the hero at a glance.
| Prop | Type | Default | What it does |
|---|---|---|---|
| variant | 'centered' | 'split' | 'left' | 'with-form' | … | 'centered' | One of 17 layouts. |
| title | ReactNode | · | The headline. Required. |
| description | ReactNode | · | The text below the headline. |
| primaryAction | { label, href } | · | The main button. |
| secondaryAction | { label, href } | · | The second button, quieter. |
| media | ReactNode | · | Image, screenshot or drawing next to or below the text. |
| labels | Partial<Texts> | · | Your own wording for built-in texts. |
| className | string | · | Extra classes on the outer element. |
Language and labels#
A few texts a component writes itself, such as Close, Next or form hints. KitProvider sets their language for everything below, English is the default. For a single place, labels overrides exactly the words you name.
<Faq
variant="with-search"
items={items}
labels={{ search: 'Search the help', placeholder: 'For example shipping' }}
/>Slots for images and media#
Images, logos, avatars and media are props of type ReactNode. Pass an <img>, a video, an SVG or a whole component, and the component places it in the right spot and frame.
<Hero
variant="split"
title="Fresh bread, every morning"
media={<img src="/bakery.avif" alt="Counter of Lindner Bakery" width={1200} height={900} />}
logos={[<Logo1 key="1" />, <Logo2 key="2" />]}
/>For device pictures there are BrowserFrame and PhoneFrame, drawn motifs live under Visuals.
Interactive in Astro#
Components render fully on the server, their content is there as HTML. When a component has state, such as an accordion, a toggle or a form, it needs a client directive in Astro. client:visible loads it once it comes into view, client:load right away.
---
import { Faq } from '@/components/ui/faq';
import { Home } from '../components/Home';
---
<!-- No state, HTML only -->
<Faq variant="grid" items={items} />
<!-- With state, as an island -->
<Home client:visible />Shared helpers#
Components of one family share a file of helpers, so forms check alike, charts read alike and scenes scroll alike. aiw add brings them along as a dependency, and you can import them yourself for your own parts.
| Helper | Import | What it does |
|---|---|---|
| form-flow | @/components/ui/form-flow | Fields as data, checks, error summary, focus on the first error, chips and the confirmation of every form section. |
| chart | @/components/ui/chart | Scales, number formats, colors, keyboard and the data table behind every chart. |
| chart-figure | @/components/ui/chart-figure | The frame of the newer charts with title, big number, change and source. |
| scroll-scene | @/components/ui/scroll-scene | Scroll progress as a CSS variable for the scroll stories and effects. |
| motion-scene | @/components/ui/motion-scene | Playback of the animated visuals, paused out of view, still with reduced motion. |
| section | @/components/ui/section | Section frame, heading block and Eyebrow shared by most sections. |
Guides with examples are under forms, charts and scroll stories.
Make it yours#
className goes to the outer element and wins on conflicts, so you adjust spacing or background without opening the file.
<Pricing variant="cards" plans={plans} className="py-32" />For anything more, open the file. It is yours, change it like your own code.