Template · React · HTML
Workspace home
A workspace start page in Tiles: a pill navbar, a large search, an inverted offer tile, deal bars and the week's schedule.
Preview
React
Install the template with shadcn. It lands in your project as ordinary components that you edit yourself.
npx shadcn@latest add https://mikkelmanniche.dk/lab/r/workspace-home.jsonnpx shadcn@latest add @manniche/workspace-homeworkspace-home.tsx
// A workspace home screen (pill navbar, large search, offer tile, deals bars, week schedule).
import { Search } from 'lucide-react'
import { useState } from 'react'
import { cn } from '@/lib/utils'
import { BarChart, type BarPoint } from '@/registry/manniche/bar-chart/bar-chart'
import { BigNumber } from '@/registry/manniche/chart-kit/chart-kit'
import { DataTile, TileFact } from '@/registry/manniche/data-tile/data-tile'
import { WeekSchedule, type ScheduleCategory, type ScheduleEvent } from '@/registry/manniche/week-schedule/week-schedule'
export type WorkspaceHomeData = {
nav: { id: string; label: string }[]
offer: { title: string; text: string; action: string }
schedule: { title: string; weekStart: string; now?: string; events: ScheduleEvent[]; categories?: ScheduleCategory[] }
deals: { title: string; total: number; points: BarPoint[]; currency?: string }
}
export type WorkspaceHomeLabels = { search?: string; searchLabel?: string; navLabel?: string; note?: string }
export type WorkspaceHomeProps = {
data: WorkspaceHomeData
labels?: WorkspaceHomeLabels
onSearch?: (query: string) => void
onOffer?: () => void
className?: string
}
export function WorkspaceHome({ data, labels, onSearch, onOffer, className }: WorkspaceHomeProps) {
const t = { search: 'Search the workspace', searchLabel: 'Search', navLabel: 'Workspace', note: 'Example data.', ...labels }
const [current, setCurrent] = useState(data.nav[0]?.id)
const [q, setQ] = useState('')
const money = { currency: data.deals.currency ?? 'EUR', decimals: 0 }
return (
<div className={cn('@container flex w-full flex-col gap-3 rounded-2xl bg-background p-3 text-foreground', className)}>
<nav aria-label={t.navLabel} className="flex gap-1 self-start overflow-x-auto rounded-full border border-border bg-card p-1">
{data.nav.map((n) => (
<button
key={n.id}
type="button"
aria-current={n.id === current ? 'page' : undefined}
onClick={() => setCurrent(n.id)}
className={cn(
'rounded-full px-4 py-1.5 text-sm text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring',
n.id === current && 'bg-foreground text-background hover:text-background',
)}
>
{n.label}
</button>
))}
</nav>
<form
role="search"
onSubmit={(e) => {
e.preventDefault()
onSearch?.(q)
}}
className="flex items-center gap-3 rounded-2xl border border-border bg-card px-5 py-4 focus-within:outline-2 focus-within:outline-ring"
>
<Search className="size-5 text-muted-foreground" aria-hidden />
<input
type="search"
value={q}
onChange={(e) => setQ(e.target.value)}
aria-label={t.searchLabel}
placeholder={t.search}
className="min-w-0 flex-1 bg-transparent text-lg outline-none placeholder:text-muted-foreground"
/>
</form>
<div className="grid grid-cols-1 gap-3 @2xl:grid-cols-5">
<div className="@2xl:col-span-2">
<DataTile className="h-full" title={data.offer.title} inverted>
<p className="text-sm opacity-80">{data.offer.text}</p>
<button
type="button"
onClick={onOffer}
className="mt-4 rounded-full bg-foreground px-4 py-2 text-sm font-medium text-background transition-transform active:scale-[0.97] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
>
{data.offer.action}
</button>
</DataTile>
</div>
<div className="@2xl:col-span-3">
<DataTile className="h-full" title={data.deals.title} footer={t.note}>
<TileFact>
<BigNumber value={data.deals.total} format={money} size="lg" />
</TileFact>
<BarChart data={data.deals.points} label={data.deals.title} format={money} />
</DataTile>
</div>
<div className="@2xl:col-span-5">
<DataTile className="h-full" title={data.schedule.title} footer={t.note}>
<WeekSchedule
data={data.schedule.events}
label={data.schedule.title}
weekStart={data.schedule.weekStart}
now={data.schedule.now}
categories={data.schedule.categories}
/>
</DataTile>
</div>
</div>
</div>
)
}workspace-home-demo.tsx
import { WorkspaceHome, type WorkspaceHomeData } from '@/components/workspace-home'
// Example data only. All names and figures are invented.
const data: WorkspaceHomeData = {
nav: [
{ id: 'home', label: 'Home' },
{ id: 'deals', label: 'Deals' },
{ id: 'people', label: 'People' },
{ id: 'files', label: 'Files' },
],
offer: { title: 'Sample offer', text: 'Example tile: a short offer with one clear action.', action: 'See the offer' },
schedule: {
title: 'This week',
weekStart: '2026-10-05',
now: '2026-10-06T10:00',
categories: [
{ id: 'call', label: 'Calls' },
{ id: 'focus', label: 'Focus' },
],
events: [
{ id: 'e1', title: 'Sample kickoff', day: 0, start: '09:00', end: '10:00', category: 'call' },
{ id: 'e2', title: 'Demo review', day: 1, start: '11:00', end: '12:00', category: 'call' },
{ id: 'e3', title: 'Example focus block', day: 2, start: '13:00', end: '15:00', category: 'focus' },
{ id: 'e4', title: 'Test planning', day: 3, start: '10:00', end: '11:30', category: 'focus' },
],
},
deals: {
title: 'Deals closed per week',
total: 84500,
points: ['W1', 'W2', 'W3', 'W4', 'W5', 'W6'].map((label, i) => ({ label, value: [9800, 14200, 12100, 17600, 15300, 15500][i] })),
},
}
export default function WorkspaceHomeDemo() {
return <WorkspaceHome data={data} />
}HTML
The whole page as one file with all CSS built in and no JavaScript. Toggles, tabs and filters work with HTML and CSS alone.
The colour in your own app
If you already have shadcn set up, these variables give the same colour. Light in :root, dark in .dark.
:root {
--primary: oklch(0.52 0.2 262);
--primary-foreground: oklch(0.99 0 0);
--accent: oklch(0.95 0.034 262);
--accent-foreground: oklch(0.36 0.140 262);
--ring: oklch(0.52 0.2 262);
}
.dark {
--primary: oklch(0.74 0.150 262);
--primary-foreground: oklch(0.18 0.024 262);
--accent: oklch(0.29 0.050 262);
--accent-foreground: oklch(0.9 0.050 262);
--ring: oklch(0.74 0.150 262);
}Install
npx shadcn@latest add https://mikkelmanniche.dk/lab/r/workspace-home.json