Recipes
Copy-paste, component-first screen compositions for consumer apps. Each recipe is a minimal file you can drop into a feature route — live component demos and prop reference live on Screen patterns.
How to use these
Paste a recipe into your app, customize props and children, then follow the Screen patterns link for the live demo and API details.
Recipes are snippets, not duplicates
Recipes stay code-only. Screen patterns holds the interactive demos for each export. Example pages (Mission Control, Settings, Data Table) show fuller product screens built from the same primitives.
Marketing landing page
/recipes/marketing-landing/PageShell + HeroBlock + FeatureSection + CTABand + Footer.
import {
PageShell, Section, Button, HeroBlock, FeatureSection,
CTABand, Footer,
} from '@tollerud/ui'
import { Zap, Shield, Activity } from 'lucide-react'
const features = [
{ icon: <Zap size={18} />, title: 'Fast deploys', description: 'Roll out compose changes with health checks.' },
{ icon: <Shield size={18} />, title: 'Guarded actions', description: 'Review risky operations before they run.' },
{ icon: <Activity size={18} />, title: 'Quiet telemetry', description: 'Surface useful state without alert fatigue.' },
]
export function MarketingLandingPage() {
return (
<PageShell background="grid">
<HeroBlock
eyebrow="homelab control plane"
title="Run your stack like production."
description="Deploy, monitor and roll back from one keyboard-first console."
actions={
<>
<Button variant="terminal">deploy --free</Button>
<Button variant="secondary">Read the docs</Button>
</>
}
/>
<Section>
<FeatureSection
eyebrow="why it works"
title="Operate with guardrails"
description="Common sections without raw utility grids."
features={features}
/>
</Section>
<CTABand
title="Ship your homelab like it matters."
description="Free for one host. No card, no telemetry, no nonsense."
actions={
<>
<Button variant="primary" size="lg">Get started</Button>
<Button variant="terminal" size="lg">view_source</Button>
</>
}
/>
<Footer />
</PageShell>
)
}Dashboard overview
/recipes/dashboard-overview/Full dashboard route: DashboardShell frames the app; StatsSection and infra cards fill the main area.
import {
Button, Card, HostCard, PageHeader, StatsSection, Stack,
DashboardShell,
} from '@tollerud/ui'
export function DashboardOverviewPage() {
return (
<DashboardShell
projectName="Mission Control"
projectSubtitle="fleet control"
pageTitle="Overview"
sidebarItems={[
{ id: 'overview', label: 'Overview', href: '/', active: true },
{ id: 'hosts', label: 'Hosts', href: '/hosts' },
{ id: 'logs', label: 'Logs', href: '/logs' },
]}
topActions={<Button size="sm" variant="primary">Deploy</Button>}
header={
<PageHeader
title="Overview"
description="Fleet health at a glance."
/>
}
>
<Stack gap="lg">
<StatsSection
title="Fleet health"
stats={[
{ label: 'Hosts online', value: 3, accent: true },
{ label: 'Open alerts', value: 1 },
{ label: 'Deploys today', value: 12 },
]}
/>
<Card>
<HostCard
hostname="emma"
ip="10.0.10.10"
status="online"
cpu="23%"
memory="6.2/16 GB"
disk="45%"
uptime="14d"
containers={4}
/>
</Card>
</Stack>
</DashboardShell>
)
}Settings page
/recipes/settings-page/Minimal SettingsLayout + FormPanel starter for copy-paste. The Settings example page is a polished account demo with icons, sticky save bar, and extra sections — same information architecture, richer shell.
'use client'
import { useState } from 'react'
import {
Button, Input, Switch, FormRow, Cluster, Stack,
SettingsLayout, FormPanel,
} from '@tollerud/ui'
export function SettingsPage() {
const [activeId, setActiveId] = useState('profile')
return (
<SettingsLayout
title="Account settings"
description="Manage profile and security preferences."
navItems={[
{ id: 'profile', label: 'Profile' },
{ id: 'security', label: 'Security' },
]}
activeId={activeId}
onNavSelect={setActiveId}
>
{activeId === 'profile' && (
<FormPanel
title="Profile"
description="Shown across the dashboard and in activity logs."
footer={<Cluster justify="end"><Button variant="primary">Save changes</Button></Cluster>}
>
<Stack gap="md">
<FormRow label="Display name">
<Input defaultValue="Tia Tollerud" />
</FormRow>
<FormRow label="Email" hint="Used for sign-in and critical alerts.">
<Input defaultValue="[email protected]" />
</FormRow>
</Stack>
</FormPanel>
)}
{activeId === 'security' && (
<FormPanel
title="Security"
description="Authentication and session controls."
footer={<Cluster justify="end"><Button variant="primary">Save changes</Button></Cluster>}
>
<FormRow label="Two-factor auth" hint="Require a TOTP code at sign-in.">
<Switch label="Enabled" defaultChecked />
</FormRow>
</FormPanel>
)}
</SettingsLayout>
)
}Auth page
/recipes/auth-page/FormPanel inside PageShell for a minimal sign-in route.
import {
PageShell, Section, Button, Input, PasswordInput,
FormPanel, Cluster,
} from '@tollerud/ui'
export function SignInPage() {
return (
<PageShell background="glow">
<Section size="hero" width="narrow">
<FormPanel
title="Sign in to your account"
description="Use your Tollerud credentials."
footer={
<Cluster justify="end">
<Button variant="ghost">Request access</Button>
<Button variant="primary">Sign in</Button>
</Cluster>
}
>
<Input label="Email" type="email" placeholder="[email protected]" />
<PasswordInput label="Password" placeholder="••••••••" />
</FormPanel>
</Section>
</PageShell>
)
}Empty state page
/recipes/empty-state-page/EmptyPage wraps EmptyState in a full-page shell.
import { Button, EmptyPage } from '@tollerud/ui'
export function NoHostsPage() {
return (
<EmptyPage
icon="server"
title="No hosts connected"
description="Connect your first machine and Tia will start watching it."
action={<Button variant="primary">Connect a host</Button>}
secondaryAction={<Button variant="ghost">Read docs</Button>}
/>
)
}Detail page
/recipes/detail-page/DetailPage composes PageHeader, primary content, and an optional aside.
import { Button, Card, StatusDot, DetailPage } from '@tollerud/ui'
export function HostDetailPage() {
return (
<DetailPage
eyebrow="host"
title="emma.tollerud.no"
description="Primary production host."
actions={<Button variant="terminal">ssh emma</Button>}
aside={<Card><StatusDot status="online" label="SSH connected" /></Card>}
>
<Card>Logs, metrics, and configuration.</Card>
</DetailPage>
)
}List / table page
/recipes/list-table-page/ResourceList wraps the page header, filters, and table body.
import { Button, Badge, Input, ResourceList, DataTable } from '@tollerud/ui'
const hosts = [
{ id: '1', hostname: 'emma', status: 'online' },
{ id: '2', hostname: 'iris', status: 'warning' },
{ id: '3', hostname: 'pia', status: 'idle' },
]
export function HostsListPage() {
return (
<ResourceList
title="Hosts"
description="Machines connected to Tollerud."
count="3 hosts"
actions={<Button variant="primary">Connect host</Button>}
filters={<Input label="Search" placeholder="emma" />}
>
<DataTable
columns={[
{ key: 'hostname', label: 'Host', sortable: true },
{
key: 'status',
label: 'Status',
render: (_v, row) => (
<Badge variant={row.status === 'online' ? 'success' : 'error'}>{row.status}</Badge>
),
},
]}
data={hosts}
rowKey="id"
searchable
pageSize={10}
/>
</ResourceList>
)
}Acceptable Tailwind glue
/recipes/escape-hatch/Small local spacing, alignment, or responsive visibility is fine. Do not rebuild branded structure with utilities.
import { Button, Card } from '@tollerud/ui'
export function DeployCard() {
return (
<Card>
<p>Ready to deploy.</p>
<div className="mt-6 flex justify-end">
<Button variant="primary">Deploy</Button>
</div>
</Card>
)
}// Avoid: bypasses Button variants, focus states, and brand tokens.
<button className="rounded-lg bg-yellow-400 px-4 py-2 text-black">
Deploy
</button>
// Avoid: move repeated branded layout into @tollerud/ui
// or a semantic feature component.
<section className="min-h-screen bg-black px-6 py-24">
<div className="mx-auto grid max-w-6xl gap-6 md:grid-cols-3">
{/* hand-built branded cards */}
</div>
</section>Full policy and setup notes live on .