Styleguide & components

Design library

Explore the visual language of muenstererOS. Inspect theme tokens, try real components, and copy working examples into your next page.

Design specification

01 / Foundations

Theme explorer

Compare every site palette in light and dark. These previews use the actual theme stylesheet and leave your saved appearance unchanged. Select a token to inspect and copy its CSS declaration.

zinc / LightPreview

A little space for your next idea.

Simple surfaces, clear type, and room to focus.

zinc / DarkPreview

A little space for your next idea.

Simple surfaces, clear type, and room to focus.

Theme tokens

Select a token to copy its declaration.
View theme CSS
.theme-zinc {
  --background: 0 0% 100%;
  --foreground: 240 10% 3.9%;
  --muted: 240 4.8% 95.9%;
  --muted-foreground: 240 3.8% 46.1%;
  --popover: 0 0% 100%;
  --popover-foreground: 240 10% 3.9%;
  --card: 0 0% 100%;
  --card-foreground: 240 10% 3.9%;
  --border: 240 5.9% 90%;
  --input: 240 5.9% 90%;
  --primary: 240 5.9% 10%;
  --primary-foreground: 0 0% 98%;
  --secondary: 240 4.8% 95.9%;
  --secondary-foreground: 240 5.9% 10%;
  --accent: 240 4.8% 95.9%;
  --accent-foreground: 240 5.9% 10%;
  --destructive: 0 72.22% 50.59%;
  --destructive-foreground: 0 0% 98%;
  --ring: 240 5.9% 10%;
  --radius: 0.5rem;
}

.dark .theme-zinc {
  --background: 240 10% 3.9%;
  --foreground: 0 0% 98%;
  --muted: 240 3.7% 15.9%;
  --muted-foreground: 240 5% 64.9%;
  --popover: 240 10% 3.9%;
  --popover-foreground: 0 0% 98%;
  --card: 240 10% 3.9%;
  --card-foreground: 0 0% 98%;
  --border: 240 3.7% 15.9%;
  --input: 240 3.7% 15.9%;
  --primary: 0 0% 98%;
  --primary-foreground: 240 5.9% 10%;
  --secondary: 240 3.7% 15.9%;
  --secondary-foreground: 0 0% 98%;
  --accent: 240 3.7% 15.9%;
  --accent-foreground: 0 0% 98%;
  --destructive: 0 62.8% 30.6%;
  --destructive-foreground: 0 0% 98%;
  --ring: 240 4.9% 83.9%;
  --radius: 0.5rem;
}

02 / Components

Try the components

Live examples use the same components as the site and follow your current site theme. Each snippet includes its imports and state. Interact with the preview, or expand the source to copy it manually.

18 Components

Accordion

Expand and collapse grouped information.

View Svelte source
<script lang="ts">
	import * as Accordion from '$lib/components/ui/accordion';
	import { i18n } from '$lib/i18n/i18n.svelte';
</script>

<Accordion.Root type="single" class="w-full">
	<Accordion.Item value="one"
		><Accordion.Trigger>{i18n.t('design.demo.details')}</Accordion.Trigger><Accordion.Content
			>{i18n.t('design.demo.description')}</Accordion.Content
		></Accordion.Item
	>
	<Accordion.Item value="two"
		><Accordion.Trigger>{i18n.t('design.demo.accessibility')}</Accordion.Trigger><Accordion.Content
			>{i18n.t('design.demo.keyboard')}</Accordion.Content
		></Accordion.Item
	>
</Accordion.Root>

Badges

Compact status labels in all four variants.

DefaultSecondaryOutlineDestructive
View Svelte source
<script lang="ts">
	import { Badge } from '$lib/components/ui/badge';
</script>

<div class="flex flex-wrap gap-2">
	<Badge>Default</Badge><Badge variant="secondary">Secondary</Badge><Badge variant="outline"
		>Outline</Badge
	><Badge variant="destructive">Destructive</Badge>
</div>

Buttons

Variants, sizes, disabled states, and click feedback.

Clicks: 0

View Svelte source
<script lang="ts">
	import { Button } from '$lib/components/ui/button';
	import { i18n } from '$lib/i18n/i18n.svelte';
	const variants = ['default', 'secondary', 'outline', 'ghost', 'link', 'destructive'] as const;
	let count = $state(0);
</script>

<div class="space-y-4">
	<div class="flex flex-wrap gap-2">
		{#each variants as variant}
			<Button {variant} onclick={() => count++}>{variant}</Button>
		{/each}
	</div>
	<div class="flex flex-wrap items-center gap-2">
		<Button size="sm" onclick={() => count++}>{i18n.t('design.demo.small')}</Button>
		<Button size="lg" onclick={() => count++}>{i18n.t('design.demo.large')}</Button>
		<Button disabled>{i18n.t('design.demo.disabled')}</Button>
	</div>
	<p class="text-sm text-muted-foreground" aria-live="polite">
		{i18n.t('design.demo.clicks')}: {count}
	</p>
</div>

Card

Compose a header, content, status, and an action.

A little space for your next idea.

Simple surfaces, clear type, and room to focus.

Draft
View Svelte source
<script lang="ts">
	import * as Card from '$lib/components/ui/card';
	import { Badge } from '$lib/components/ui/badge';
	import { Button } from '$lib/components/ui/button';
	import { i18n } from '$lib/i18n/i18n.svelte';
	let saved = $state(false);
</script>

<Card.Root class="w-full max-w-sm">
	<Card.Header
		><Card.Title>{i18n.t('design.demo.project')}</Card.Title><Card.Description
			>{i18n.t('design.demo.description')}</Card.Description
		></Card.Header
	>
	<Card.Content
		><Badge variant="secondary">{i18n.t(saved ? 'design.demo.saved' : 'design.demo.draft')}</Badge
		></Card.Content
	>
	<Card.Footer
		><Button onclick={() => (saved = !saved)}
			>{i18n.t(saved ? 'design.demo.reset' : 'design.demo.save')}</Button
		></Card.Footer
	>
</Card.Root>

Command bar

Search and select commands in an isolated palette.

Use Tab to focus controls and arrow keys to move between options.

View Svelte source
<script lang="ts">
	import * as Command from '$lib/components/ui/command';
	import { i18n } from '$lib/i18n/i18n.svelte';
	let selected = $state('');
</script>

<div class="w-full space-y-3">
	<Command.Root class="rounded-md border" label={i18n.t('design.command')}>
		<Command.Input placeholder={i18n.t('design.search')} aria-label={i18n.t('design.search')} />
		<Command.List
			><Command.Empty>{i18n.t('design.empty')}</Command.Empty><Command.Group
				value="components"
				heading={i18n.t('design.components')}
			>
				{#each ['cards', 'buttons', 'inputs'] as item}<Command.Item
						value={item}
						keywords={[i18n.t('design.examples.' + item + '.title')]}
						onSelect={() => (selected = item)}
						>{i18n.t('design.examples.' + item + '.title')}</Command.Item
					>{/each}
			</Command.Group></Command.List
		>
	</Command.Root>
	<p class="min-h-5 text-sm text-muted-foreground" aria-live="polite">
		{selected ? i18n.t('design.examples.' + selected + '.title') : i18n.t('design.demo.keyboard')}
	</p>
</div>

Dialog

Test modal focus, dismissal, and focus restoration.

View Svelte source
<script lang="ts">
	import * as Dialog from '$lib/components/ui/dialog';
	import { Button } from '$lib/components/ui/button';
	import { i18n } from '$lib/i18n/i18n.svelte';
	let open = $state(false);
</script>

<Dialog.Root bind:open>
	<Dialog.Trigger class="rounded-md border px-4 py-2 text-sm hover:bg-accent"
		>{i18n.t('design.demo.open')}</Dialog.Trigger
	>
	<Dialog.Content class="w-[calc(100%-2rem)] rounded-lg">
		<Dialog.Header
			><Dialog.Title>{i18n.t('design.demo.project')}</Dialog.Title><Dialog.Description
				>{i18n.t('design.demo.dialog')}</Dialog.Description
			></Dialog.Header
		>
		<Dialog.Footer
			><Button onclick={() => (open = false)}>{i18n.t('design.demo.done')}</Button></Dialog.Footer
		>
	</Dialog.Content>
</Dialog.Root>

Inputs & textarea

Labeled fields, editable values, and disabled states.

View Svelte source
<script lang="ts">
	import { Input } from '$lib/components/ui/input';
	import { Textarea } from '$lib/components/ui/textarea';
	import { Label } from '$lib/components/ui/label';
	import { i18n } from '$lib/i18n/i18n.svelte';
	let { id = 'design-inputs' }: { id?: string } = $props();
	let name = $state('');
	let note = $state('');
</script>

<div class="w-full max-w-sm space-y-3">
	<Label for={id + '-name'}>{i18n.t('design.demo.name')}</Label>
	<Input id={id + '-name'} bind:value={name} placeholder={i18n.t('design.demo.name')} />
	<Label for={id + '-note'}>{i18n.t('design.demo.note')}</Label>
	<Textarea id={id + '-note'} bind:value={note} placeholder={i18n.t('design.demo.write')} />
	<Input
		aria-label={i18n.t('design.demo.disabled')}
		disabled
		placeholder={i18n.t('design.demo.disabled')}
	/>
</div>

Markdown preview

Edit Markdown and inspect the shared typography renderers live.

Rendered preview

A small document

Markdown keeps content portable while shared renderers keep it at home in the system.

Quiet structure leaves room for the work.

  • Semantic headings

  • Familiar links

  • Reusable code blocks

ts
const ready = true;
View Svelte source
<script lang="ts">
	import { MdSvelte } from '@jazzymcjazz/mdsvelte';
	import { renderers } from '$lib/components/typography';
	import { Textarea } from '$lib/components/ui/textarea';
	import { Label } from '$lib/components/ui/label';
	import { i18n } from '$lib/i18n/i18n.svelte';
	import SafeHtml from '$lib/components/design/SafeHtml.svelte';

	const markdownRenderers = { ...renderers, html: SafeHtml };

	let source = $state(`# A small document

Markdown keeps content **portable** while shared renderers keep it at home in the system.

> Quiet structure leaves room for the work.

- Semantic headings
- Familiar links
- Reusable code blocks

\`\`\`ts
const ready = true;
\`\`\``);
</script>

<div class="grid w-full gap-4 xl:grid-cols-2">
	<div class="space-y-2">
		<Label for="design-markdown-source">{i18n.t('design.markdown_source')}</Label>
		<Textarea
			id="design-markdown-source"
			bind:value={source}
			class="min-h-80 resize-y font-mono text-sm"
		/>
	</div>
	<div class="space-y-2">
		<p class="text-sm font-medium">{i18n.t('design.markdown_preview')}</p>
		<div class="prose dark:prose-invert min-h-80 rounded-md border bg-background p-5">
			<MdSvelte {source} renderers={markdownRenderers} />
		</div>
	</div>
</div>

Dropdown menu

Open actions, select an item, and inspect feedback.

Draft

View Svelte source
<script lang="ts">
	import * as Menu from '$lib/components/ui/dropdown-menu';
	import { i18n } from '$lib/i18n/i18n.svelte';
	let saved = $state(false);
</script>

<div class="space-y-3">
	<Menu.Root>
		<Menu.Trigger class="rounded-md border px-4 py-2 text-sm hover:bg-accent"
			>{i18n.t('design.demo.details')}</Menu.Trigger
		>
		<Menu.Content
			><Menu.Item onSelect={() => (saved = true)}>{i18n.t('design.demo.save')}</Menu.Item
			><Menu.Separator /><Menu.Item onSelect={() => (saved = false)}
				>{i18n.t('design.demo.reset')}</Menu.Item
			><Menu.Item disabled>{i18n.t('design.demo.disabled')}</Menu.Item></Menu.Content
		>
	</Menu.Root>
	<p class="text-sm text-muted-foreground" aria-live="polite">
		{i18n.t(saved ? 'design.demo.saved' : 'design.demo.draft')}
	</p>
</div>

Select

Choose a value from a labeled list.

Status: Draft

View Svelte source
<script lang="ts">
	import * as Select from '$lib/components/ui/select';
	import { i18n } from '$lib/i18n/i18n.svelte';
	let value = $state('draft');
</script>

<div class="w-full max-w-sm space-y-3">
	<Select.Root type="single" bind:value>
		<Select.Trigger aria-label={i18n.t('design.demo.status')}
			>{i18n.t('design.demo.' + value)}</Select.Trigger
		>
		<Select.Content
			><Select.Item value="draft">{i18n.t('design.demo.draft')}</Select.Item><Select.Item
				value="saved">{i18n.t('design.demo.saved')}</Select.Item
			></Select.Content
		>
	</Select.Root>
	<p class="text-sm text-muted-foreground" aria-live="polite">
		{i18n.t('design.demo.status')}: {i18n.t('design.demo.' + value)}
	</p>
</div>

Skeleton

Compare loading placeholders with loaded content.

View Svelte source
<script lang="ts">
	import { Skeleton } from '$lib/components/ui/skeleton';
	import { Button } from '$lib/components/ui/button';
	import { i18n } from '$lib/i18n/i18n.svelte';
	let loading = $state(true);
</script>

<div class="w-full max-w-sm space-y-4">
	<div aria-busy={loading} aria-label={i18n.t('design.demo.project')} class="min-h-20">
		{#if loading}<div class="flex gap-4">
				<Skeleton class="size-12 rounded-full motion-reduce:animate-none" />
				<div class="flex-1 space-y-3">
					<Skeleton class="h-4 w-3/4 motion-reduce:animate-none" /><Skeleton
						class="h-4 w-full motion-reduce:animate-none"
					/>
				</div>
			</div>
		{:else}<p>{i18n.t('design.demo.description')}</p>{/if}
	</div>
	<Button variant="outline" onclick={() => (loading = !loading)}
		>{i18n.t(loading ? 'design.demo.loaded' : 'design.demo.reset')}</Button
	>
</div>

Slider & progress

Adjust a value and watch the progress indicator respond.

Progress: 40%

View Svelte source
<script lang="ts">
	import { Slider } from '$lib/components/ui/slider';
	import { Progress } from '$lib/components/ui/progress';
	import { i18n } from '$lib/i18n/i18n.svelte';
	let value = $state(40);
</script>

<div class="w-full max-w-sm space-y-5">
	<p>{i18n.t('design.demo.progress')}: {value}%</p>
	<Slider
		type="single"
		bind:value
		min={0}
		max={100}
		step={5}
		aria-label={i18n.t('design.demo.progress')}
	/>
	<Progress {value} aria-label={i18n.t('design.demo.progress')} />
</div>

Table

Semantic rows, headers, captions, and status labels.

Simple surfaces, clear type, and room to focus.
NameStatus
AlphaDraft
BetaDraft
View Svelte source
<script lang="ts">
	import * as Table from '$lib/components/ui/table';
	import { Badge } from '$lib/components/ui/badge';
	import { i18n } from '$lib/i18n/i18n.svelte';
</script>

<Table.Root>
	<Table.Caption>{i18n.t('design.demo.description')}</Table.Caption>
	<Table.Header
		><Table.Row
			><Table.Head>{i18n.t('design.demo.name')}</Table.Head><Table.Head
				>{i18n.t('design.demo.status')}</Table.Head
			></Table.Row
		></Table.Header
	>
	<Table.Body
		>{#each ['Alpha', 'Beta'] as name}<Table.Row
				><Table.Cell>{name}</Table.Cell><Table.Cell
					><Badge variant="outline">{i18n.t('design.demo.draft')}</Badge></Table.Cell
				></Table.Row
			>{/each}</Table.Body
	>
</Table.Root>

Tabs

Switch panels with pointer or keyboard.

Simple surfaces, clear type, and room to focus.
View Svelte source
<script lang="ts">
	import * as Tabs from '$lib/components/ui/tabs';
	import { i18n } from '$lib/i18n/i18n.svelte';
</script>

<Tabs.Root value="preview" class="w-full">
	<Tabs.List aria-label={i18n.t('design.examples.tabs.title')}>
		<Tabs.Trigger value="preview">{i18n.t('design.preview')}</Tabs.Trigger>
		<Tabs.Trigger value="details">{i18n.t('design.demo.details')}</Tabs.Trigger>
	</Tabs.List>
	<Tabs.Content value="preview" class="p-4">{i18n.t('design.demo.description')}</Tabs.Content>
	<Tabs.Content value="details" class="p-4">{i18n.t('design.demo.keyboard')}</Tabs.Content>
</Tabs.Root>

Checkbox & switch

Toggle independent options and inspect their state.

Notifications: On

View Svelte source
<script lang="ts">
	import { Checkbox } from '$lib/components/ui/checkbox';
	import { Switch } from '$lib/components/ui/switch';
	import { Label } from '$lib/components/ui/label';
	import { i18n } from '$lib/i18n/i18n.svelte';
	let { id = 'design-toggles' }: { id?: string } = $props();
	let checked = $state(false);
	let enabled = $state(true);
</script>

<div class="space-y-5">
	<div class="flex min-h-11 items-center gap-3">
		<Checkbox id={id + '-check'} bind:checked /><Label for={id + '-check'}
			>{i18n.t('design.demo.check')}</Label
		>
	</div>
	<div class="flex min-h-11 items-center gap-3">
		<Switch id={id + '-switch'} bind:checked={enabled} /><Label for={id + '-switch'}
			>{i18n.t('design.demo.notifications')}</Label
		>
	</div>
	<p class="text-sm text-muted-foreground" aria-live="polite">
		{i18n.t('design.demo.notifications')}: {i18n.t(enabled ? 'design.demo.on' : 'design.demo.off')}
	</p>
</div>

Tooltip

Supplementary hints on hover and keyboard focus.

View Svelte source
<script lang="ts">
	import * as Tooltip from '$lib/components/ui/tooltip';
	import { i18n } from '$lib/i18n/i18n.svelte';
</script>

<Tooltip.Provider delayDuration={200}>
	<Tooltip.Root>
		<Tooltip.Trigger class="rounded-md border px-4 py-2 text-sm hover:bg-accent"
			>{i18n.t('design.demo.accessibility')}</Tooltip.Trigger
		>
		<Tooltip.Content role="tooltip" class="max-w-xs"
			>{i18n.t('design.demo.keyboard')}</Tooltip.Content
		>
	</Tooltip.Root>
</Tooltip.Provider>

Typography

Display, headings, body text, links, keyboard hints, and code.

Display / 48

Calm, precise, and readable.

Heading / 36

A clear hierarchy

Body / 16

System typography should disappear into the reading experience. Strong hierarchy, comfortable measure, and restrained weights keep dense interfaces easy to scan.

Muted text supports the content without competing with it.

Design specification Press ⌘ K --foreground
View Svelte source
<script lang="ts">
	import { Heading, Kbd, Link } from '$lib/components/typography';
	import { i18n } from '$lib/i18n/i18n.svelte';
</script>

<div class="w-full space-y-8">
	<div class="space-y-2">
		<p class="font-mono text-xs uppercase tracking-widest text-muted-foreground">Display / 48</p>
		<p class="text-4xl font-bold leading-none tracking-tight sm:text-5xl">
			{i18n.t('design.demo.typography_title')}
		</p>
	</div>
	<div class="space-y-2">
		<p class="font-mono text-xs uppercase tracking-widest text-muted-foreground">Heading / 36</p>
		<Heading depth={2} class="border-0 pb-0">{i18n.t('design.demo.typography_heading')}</Heading>
	</div>
	<div class="space-y-2">
		<p class="font-mono text-xs uppercase tracking-widest text-muted-foreground">Body / 16</p>
		<p class="max-w-2xl leading-relaxed">{i18n.t('design.demo.typography_body')}</p>
		<p class="text-sm text-muted-foreground">{i18n.t('design.demo.typography_muted')}</p>
	</div>
	<div class="flex flex-wrap items-center gap-4">
		<Link href="/design.md">{i18n.t('design.spec')}</Link>
		<span class="text-sm text-muted-foreground">{i18n.t('design.demo.press')} <Kbd>⌘ K</Kbd></span>
		<code class="rounded bg-muted px-2 py-1 font-mono text-sm">--foreground</code>
	</div>
</div>

Window

The system shell with working window controls.

A little space for your next idea.

Try the close, minimize, and maximize controls.

View Svelte source
<script lang="ts">
	import { Window } from '$lib/components/ui/window';
	import { Button } from '$lib/components/ui/button';
	import { i18n } from '$lib/i18n/i18n.svelte';
	let closed = $state(false);
	let minimized = $state(false);
	let maximized = $state(false);
</script>

<div class="w-full">
	{#if closed}
		<Button variant="outline" onclick={() => (closed = false)}
			>{i18n.t('design.demo.restore')}</Button
		>
	{:else}
		<Window
			class={maximized ? 'w-full' : 'max-w-sm'}
			childClassName={minimized ? 'hidden' : ''}
			onClose={() => (closed = true)}
			onMinimize={() => (minimized = !minimized)}
			onMaximize={() => (maximized = !maximized)}
			isMaximized={maximized}
		>
			{#snippet titlebarCenter()}<span class="text-xs">{i18n.t('design.demo.project')}</span
				>{/snippet}
			<p class="text-sm">{i18n.t('design.demo.window')}</p>
		</Window>
	{/if}
</div>

03 / Guidelines

Build with the system

Design specification

The specification defines typography, spacing, colors, motion, and accessibility. The examples here show the current implementation.

DESIGN.md ↗

Keep the command bar complete

Every new page or user-facing action belongs in the command bar. Add clear labels, search keywords, and translations in the same change.

Reuse and extend

Copy an example into a Svelte page and adapt its content. Keep shared components and semantic tokens; add a library example when introducing a new reusable pattern.