shadcn
Stock shadcn-svelte components.
Input
<script lang="ts">
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
</script>
<div class="flex max-w-sm flex-col gap-2">
<Label for="demo-name">Device name</Label>
<Input id="demo-name" placeholder="abby's zune hd" />
</div>Switch
<script lang="ts">
import { Switch } from '$lib/components/ui/switch/index.js';
import { Label } from '$lib/components/ui/label/index.js';
let on = $state(true);
</script>
<div class="flex items-center gap-3">
<Switch id="demo-sync" bind:checked={on} />
<Label for="demo-sync">Sync when the device connects: {on ? 'on' : 'off'}</Label>
</div>Slider
60
<script lang="ts">
import { Slider } from '$lib/components/ui/slider/index.js';
let volume = $state(60);
</script>
<div class="flex max-w-sm items-center gap-4">
<Slider type="single" bind:value={volume} max={100} step={1} />
<span class="fk-data w-10 text-right">{volume}</span>
</div>Progress
<script lang="ts">
import { Progress } from '$lib/components/ui/progress/index.js';
import { Button } from '$lib/components/ui/button/index.js';
let value = $state(40);
</script>
<div class="flex max-w-sm flex-col gap-4">
<Progress {value} max={100} />
<Button variant="outline" class="w-fit" onclick={() => (value = (value + 20) % 120)}>
send more songs
</Button>
</div>Separator
fluorokit
A Metro-style design system.
tokens themes base css
<script lang="ts">
import { Separator } from '$lib/components/ui/separator/index.js';
</script>
<div class="max-w-sm">
<p class="fk-subheading">fluorokit</p>
<p class="fk-small fk-muted">A Metro-style design system.</p>
<Separator class="my-4" />
<div class="flex h-5 items-center gap-4 text-sm">
<span>tokens</span>
<Separator orientation="vertical" />
<span>themes</span>
<Separator orientation="vertical" />
<span>base css</span>
</div>
</div>Skeleton
<script lang="ts">
import { Skeleton } from '$lib/components/ui/skeleton/index.js';
</script>
<div class="flex items-center gap-4">
<Skeleton class="size-12" />
<div class="flex flex-col gap-2">
<Skeleton class="h-4 w-56" />
<Skeleton class="h-4 w-40" />
</div>
</div>Tooltip
<script lang="ts">
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
import { buttonVariants } from '$lib/components/ui/button/index.js';
</script>
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger class={buttonVariants({ variant: 'outline' })}>hover me</Tooltip.Trigger>
<Tooltip.Content>
<p>Send to the device</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>Alert dialog
<script lang="ts">
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
import { buttonVariants } from '$lib/components/ui/button/index.js';
</script>
<AlertDialog.Root>
<AlertDialog.Trigger class={buttonVariants({ variant: 'outline' })}>remove the device</AlertDialog.Trigger>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>Remove this device?</AlertDialog.Title>
<AlertDialog.Description>
The songs stay on the device. It will not sync again until you set it up.
</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
<AlertDialog.Action>Remove</AlertDialog.Action>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>Scroll area
artist
100 gecs
Frost Children
Jane Remover
Porter Robinson
Sewerslvt
torr
webcage
underscores
quannnic
glaive
ericdoa
brakence
<script lang="ts">
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
const ARTISTS = ['100 gecs', 'Frost Children', 'Jane Remover', 'Porter Robinson', 'Sewerslvt',
'torr', 'webcage', 'underscores', 'quannnic', 'glaive', 'ericdoa', 'brakence'];
</script>
<ScrollArea class="h-40 w-56 border">
<div class="p-3">
<p class="fk-label mb-2">artist</p>
{#each ARTISTS as artist (artist)}
<p class="py-1 text-sm">{artist}</p>
{/each}
</div>
</ScrollArea>extras
Fluorokit Metro components.
Type
Each class sets size, line height, weight and tracking together.
overline
hero
title
Heading
Subheading
Body text.
Small text.
Label
2026-11-15 · 152px
<p class="fk-overline">overline</p>
<p class="fk-hero">hero</p>
<p class="fk-title">title</p>
<p class="fk-heading">Heading</p>
<p class="fk-subheading">Subheading</p>
<p>Body text.</p>
<p class="fk-small">Small text.</p>
<p class="fk-label">Label</p>
<p class="fk-data">2026-11-15 · 152px</p>Pivot
Clicking a header sets the side the next page slides in from. On a site of separate pages, a nav of links with the same classes does the same thing (see the code tab).
- 10,000 gecs
- Frailty
- Worlds
<script lang="ts">
import Pivot from 'fluorokit/svelte/Pivot.svelte';
const PAGES = ['music', 'videos', 'pictures', 'podcasts'] as const;
const ROWS = {
music: ['10,000 gecs', 'Frailty', 'Worlds'],
videos: ['launch day', 'static fire', 'teardown'],
pictures: ['san francisco', 'tulsa', 'the workshop'],
podcasts: ['off-nominal', 'the vergecast'],
};
let page = $state<(typeof PAGES)[number]>('music');
</script>
<Pivot items={PAGES} bind:value={page} />
<!-- Keyed on the page, so the rows enter again on each change. -->
{#key page}
<ul class="mt-4 min-h-28 overflow-hidden">
{#each ROWS[page] as row, i (row)}
<li class="fk-enter py-1" style="--i: {i}">{row}</li>
{/each}
</ul>
{/key}
<!--
As links, for a site of separate pages. Put enterDirectionScript (from
'fluorokit/enter') in the <head> with the hrefs in nav order:
<nav class="fk-pivot fk-pivot-sub">
<a href="/" aria-current="page">home</a>
<a href="/blog/">blog</a>
</nav>
-->Page enter
--i staggers siblings; --fk-enter-dir picks the side.
first
second
third
<script lang="ts">
import { Button } from '$lib/components/ui/button/index.js';
// 1 comes from the right, -1 from the left. The key starts the animation again.
let dir = $state(1);
let run = $state(0);
const play = (side: number) => {
dir = side;
run += 1;
};
</script>
{#key run}
<div class="overflow-hidden" style="--fk-enter-dir: {dir}">
<p class="fk-enter fk-subheading" style="--i: 0">first</p>
<p class="fk-enter fk-subheading" style="--i: 1">second</p>
<p class="fk-enter fk-subheading" style="--i: 2">third</p>
</div>
{/key}
<div class="mt-6 flex gap-2">
<Button variant="outline" onclick={() => play(-1)}>from the left</Button>
<Button variant="outline" onclick={() => play(1)}>from the right</Button>
</div>Tiles
Square, wide or large. The fills come from the theme.
<div class="fk-tile-grid" style="max-width: calc(4 * var(--fk-tile-1) + 3 * var(--fk-tile-gap))">
<a class="fk-tile" data-size="large" href="#tiles" style="--fk-tile-color: var(--fk-tile-red)">
<span class="fk-overline">music</span>
</a>
<a class="fk-tile" data-size="wide" href="#tiles" style="--fk-tile-color: var(--fk-tile-lavender)">
<span class="fk-overline">blog</span>
</a>
<a class="fk-tile" href="#tiles" style="--fk-tile-color: var(--fk-tile-sky)">
<span class="fk-overline">about</span>
</a>
<a class="fk-tile" href="#tiles" style="--fk-tile-color: var(--fk-tile-green)">
<span class="fk-overline">guestbook</span>
</a>
</div>Status
Active Degraded Down Deploying Archived
<div class="flex flex-wrap gap-6">
<span class="fk-status ok">Active</span>
<span class="fk-status warn">Degraded</span>
<span class="fk-status error">Down</span>
<span class="fk-status info">Deploying</span>
<span class="fk-status">Archived</span>
</div>Energy bar
The one gradient. Use it once per view at most.
<div class="flex flex-col gap-4">
<div class="fk-bar" style="height: var(--fk-bar-edge)"></div>
<div class="fk-bar" style="height: var(--fk-bar-field)"></div>
</div>