MonoStack

@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" />

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} />

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

On this page