@repo/components
UI components built on shadcn-svelte with custom additions.
Usage
<script lang="ts">
import { Button, Card, Dialog } from '@repo/components'
</script>shadcn-svelte Components
All standard shadcn-svelte components are included:
Accordion, Alert, AlertDialog, AspectRatio, Avatar, Badge, Breadcrumb, Button, Calendar, Card, Carousel, Chart, Checkbox, Collapsible, Command, ContextMenu, Dialog, Drawer, DropdownMenu, Form, HoverCard, Input, InputGroup, InputOTP, Label, Menubar, NavigationMenu, Pagination, Popover, Progress, RadioGroup, RangeCalendar, Resizable, ScrollArea, Select, Separator, Sheet, Sidebar, Skeleton, Slider, Sonner, Switch, Table, Tabs, Textarea, Toggle, ToggleGroup, Tooltip
Custom Components
Headers
Page headers for different contexts:
<script lang="ts">
import { page } from '$app/state'
import { Button, DetailHeader, Header, InsertHeader } from '@repo/components'
</script>
<!-- Actions render beside the title, or standalone when no title is supplied -->
<Header title="Users">
<Button.Root>Invite</Button.Root>
</Header>
<!-- Detail page with optional delete and audit logs -->
<DetailHeader
id={page.params.id as string}
title="User"
deleteForm={canDelete && deleteUserForm}
getAuditLogs={getAuditLogQuery}
table="users"
/>
<!-- Use a snippet for custom title markup -->
<DetailHeader>
{#snippet title()}<span class="font-semibold">User profile</span>{/snippet}
</DetailHeader>
<!-- Insert/create page -->
<InsertHeader title="New User" />Sidebar
Application sidebar with navigation:
<script lang="ts">
import { page } from '$app/state'
import { AppSidebar, NavMain, Sidebar } from '@repo/components'
</script>
{#snippet sidebarHeader()}
<NavMain class="p-0" menu={organizationMenu} pathname={page.url.pathname} />
{/snippet}
<Sidebar.Provider>
<AppSidebar header={sidebarHeader} {menu} {user} pathname={page.url.pathname} />
<Sidebar.Inset>
{@render children()}
</Sidebar.Inset>
</Sidebar.Provider>Menu items use either href or onclick. Parent-only items can omit both and provide items.
Top Bar
Responsive website navigation. Groups render as a navigation menu on desktop and collapsibles in a mobile sheet.
<script lang="ts">
import { page } from '$app/state'
import { TopBar } from '@repo/components'
const menuItems: TopBar.MenuItem[] = [
{ title: 'Home', href: '/' },
{
title: 'Company',
items: [{ title: 'About', href: '/about' }],
},
]
</script>
<TopBar.Root {menuItems} pathname={page.url.pathname}>
{#snippet logo()}<img src="/logo.svg" alt="Company" />{/snippet}
</TopBar.Root>Onboarding
Onboarding.Root owns the dialog, progress, and navigation. Declare each screen with Onboarding.Step; use manualAdvance when its content advances the flow itself.
<script lang="ts">
import { Button, Onboarding } from '@repo/components'
import Sparkles from '@lucide/svelte/icons/sparkles'
let open = $state(true)
</script>
<Onboarding.Root bind:open>
<Onboarding.Step title="Welcome" description="Set up your account" icon={Sparkles}>
<p>A short introduction for this application.</p>
</Onboarding.Step>
{#snippet complete({ close })}
<Button.Root onclick={close}>Get started</Button.Root>
{/snippet}
</Onboarding.Root>Buttons
Button variants use a pointer cursor for enabled buttons and button links.
import {(ConfirmationButton, CopyButton)} from '@repo/components' import Trash2 from '@lucide/svelte/icons/trash-2'
<!-- Button with confirmation dialog -->
<ConfirmationButton label="Delete" icon={Trash2} remoteForm={deleteForm} />
<!-- Copy text to clipboard -->
<CopyButton text={apiKey} />Breadcrumbs
Auto-generated breadcrumbs from page data:
import {AutomaticBreadcrumbs} from '@repo/components'
<AutomaticBreadcrumbs pageTitles={page.data.breadcrumbs} />SEO Meta
Render head tags once from layout or page metadata:
<script lang="ts">
import { SEOMeta } from '@repo/components'
const defaultMeta = {
title: 'My App',
description: 'Default app description',
siteName: 'My App',
}
</script>
<SEOMeta pageMeta={page.data.pageMeta} {defaultMeta} title={(title) => `${title} | My App`} />SEOMeta owns its <svelte:head> output, so do not wrap it in another <svelte:head> block.
If pageMeta.canonicalUrl and defaultMeta.canonicalUrl are missing, it falls back to PUBLIC_APP_URL plus the current route pathname.
DefinitionList
Renders an HTML <dl> with consistent label/value styling. Use for record info on detail pages and card metadata.
import {DefinitionList} from '@repo/components'
<!-- 2-column (defaults to 1) -->
<DefinitionList.Root columns={2}>
<DefinitionList.Item label="Name">{user.name}</DefinitionList.Item>
<DefinitionList.Item label="Email">{user.email}</DefinitionList.Item>
</DefinitionList.Root>columns: 1 | 2 | 3 (default 1). Override gap/padding via class prop.
Styling
<!-- routes/+layout.svelte -->
<script lang="ts">
import { StyleProvider } from '@repo/components'
import { buttonVariants } from '$lib/library/styling'
</script>
<StyleProvider.Root {buttonVariants}>
{@render children?.()}
</StyleProvider.Root>Updating Components
Update all shadcn-svelte components and normalize indentation:
pnpm exec vp run --filter @repo/components update-components