Skip to content
On this page Page layout

Getting started

Choosing a component

Decision tables for the pairs and groups of components that look alike, so you pick the right one before writing code.

Several components solve a similar problem and differ in one rule. Find your situation in the table, use the component in the second column, and read its page for props. If a row does not fit, call get_component or search_demos before writing a wrapper, see Common workarounds.

Page layout

A route is one of these templates. They own the header, the scroll area, the footer and the mobile layout, so you fill snippets instead of building a wrapper.

If you need Use Why / the rule
A list or CRUD index with search, filters and paging AppList Takes data, searchKeys, perPage. Snippets: actions, filters, toolbar, empty, footer, children.
Create or edit one entity AppForm Fixed header, scrolling body, sticky footer snippet for Cancel and Save. Fields go in FormSection.
Settings with several sections AppSettings Tabbed sections built from FormSection and SettingItem.
Any other content page (dashboard, detail) PageShell title, description, actions snippet. gap is a number in px.
A single-page app with no rail or bottom navigation PageShell in your own <main id="main" tabindex="-1"> PageShell never renders <main> and AppShellLayout with one module still draws a one-icon rail. Add a SkipLink first. See Standalone page.
Sign in, sign up, password reset AuthLayout Renders the page <main>: do not add another. AuthCard holds the form.
A 404, 403, 500 or 503 page ErrorPage Localized copy, home and back actions.
A section or a list with nothing in it EmptyState Icon, title, description, actions. In AppList use its empty snippet instead.
A page with a side column, or a narrow form or settings page PageShell aside {#snippet aside()} sits next to the content, asideLabel names it. width="reading" caps the column near 46rem.
A map-first page with a floating panel PageFrame mode map, split or page; sheet makes a bottom sheet on a phone. It tells the stage what it covers through stageInsets.
A background that stays mounted across routes (map, canvas, video) AppShellLayout stage {#snippet stage({ hidden })}; stageVisible={false} hides and pauses it on a full-page route.
Rail captions, a search trigger, more than 5 modules AppShellLayout railLabels, {#snippet railTop()} for a search or Cmd+K button, bottomNavPrimary and moreSheet={NavMoreSheet} for the mobile “More” sheet. collapseShortPanel closes the panel for a module with 2 destinations or fewer.
A page or block in another preset or scheme ThemeIsland preset, scheme, and document for a full page. setRouteTheme (from @codeatlasdev/ui/kit) sets it per route with SSR parity. data-theme-scope alone gives a local island with no SSR parity.
Site navigation in a marketing header, with a phone menu NavigationMenu in the SiteHeader trailing snippet, a Drawer below 600px NavigationMenu must be a direct child of trailing; the header hides it below 600px. Keep the LocaleSwitcher and one CTA in the bar. See Landing page.
A language picker in the header LocaleSwitcher options as data, onselect, compact. Put it in the SiteHeader trailing snippet.
A sign in with the art on the right or full bleed AuthLayout panelSide, panelBleed, align="start", panelHidden. A password field takes revealable on TextField.

Never: wrap a page in your own div with a title row and a footer bar. Never put AppShellLayout in public or auth routes, only in the signed-in layout.

Maps

If you need Use Why / the rule
An interactive map behind the page MapStage From @codeatlasdev/ui/map. Needs the peer maplibre-gl 5 or 6 (default, no token; use 6.4.1 or later, and wire setWorkerUrl on 6) or mapbox-gl (accessToken), plus @deck.gl/core and @deck.gl/maplibre (@deck.gl/mapbox for mapbox-gl) for layers. Basemap per scheme in styles.
Zoom, compass and locate buttons MapControls Same entry. A child of MapStage.
Your own layers in the theme colors useMapTheme Same entry. theme.color(token) returns an Rgba that follows the live preset.
A panel that floats over the map PageFrame It sets the map padding for you. Put MapStage in the AppShellLayout stage snippet so it survives navigation.
A list of places the user picks to recenter the map Button variant="ghost" tone="neutral" align="between" fullWidth with aria-pressed, in Stack as="ul" No list component has a row action: RecordRow has none, TableRow is for tables. Recenter with the map from onmapready. See Map page.
A static place or an address Image or a link to a map app No @codeatlasdev/ui/map import: it pulls in a map engine.

Never: create a map in onMount yourself, or hand-measure the panel to offset the map.

Overlay

Every overlay below is a native <dialog> or a popover element in the browser top layer, so none needs a z-index. Pick by how the user got there and what they must answer.

If you need Use Why / the rule
A modal for a form or information Dialog bind:open, title, actions snippet. Escape and an overlay press close it.
A yes or no the user must answer (delete, discard) AlertDialog role="alertdialog", onconfirm may be async, destructive, pending.
A second panel from the left or right edge (filters, details, settings) Drawer side, width, actions footer. Left suits navigation.
A mobile bottom panel with swipe to dismiss Sheet Bottom sheet below 900px, centered dialog above. footer snippet.
A small card anchored to a button (short content, a mini form) Popover triggerChild receives the trigger props; children receives close.
The main navigation of a marketing header, with grouped panels of links NavigationMenu Disclosure buttons and links, not role=menu. Application commands are Menubar.
An application menu bar (File, Edit, View) Menubar MenubarMenu label; its content is the DropdownMenu items. Arrow keys move between and across menus.
A list of actions on a button DropdownMenu Items, checkbox and radio items, submenus. Sub-components are named exports, not dotted.
A menu on right-click, long-press or Shift+F10 over a region ContextMenu The menu snippet takes the DropdownMenu items. Never the only way to reach an action.
A rich preview of a link or person on hover and focus (touch: the link’s own action) HoverCard openDelay, closeDelay, Tab reaches links inside. Tap-to-open content is Popover.
A label on hover or focus Tooltip Text only. Never put a control or the only copy of a fact in it.
Your own hover or edge UI that must not open under a dialog layers store layers.modalOpen, layers.blocks(trigger). Kit hover UI already obeys it.
A full-screen mobile drill-down (chat thread, detail) MobilePane Fixed, safe-area aware, back button. Replaces a hand-made fixed panel.
Photos or videos at full size Lightbox src and alt for one image, items for a gallery. alt is required.
Jump to a route or run a command from the keyboard CommandPalette Opens with Cmd+K or Ctrl+K. Takes your modules and onNavigate.
A side detail panel with its own header Drawer chromeless No built-in header or padding. Add fullBleedBelow to fill the screen on a phone.

Drawer has two docs pages and one component. Use Drawer in Feedback for the props table, the bottom side and the footer. Drawer (navigation) only shows the left menu and right settings panel patterns of the same component.

Drawer chromeless drops the header, close button and body padding for a side detail panel whose content brings its own header and scroll; name it with title, and add fullBleedBelow="md" to fill a phone screen. Sheet is modal. PageFrame sheet is a non-modal bottom sheet over a map, so the stage stays interactive.

Never: build a fixed full-screen panel by hand, set z-index on an overlay, or use Dialog for a confirmation that needs an explicit answer.

Table and lists

If you need Use Why / the rule
A plain table with your own markup SimpleTable Sticky header, hover rows. responsive="cards" turns rows into cards in a narrow container.
Sorting, column visibility, resizing, row groups, many columns DataTable Import from @codeatlasdev/ui/table. Needs the optional peer @tanstack/svelte-table.
Records as dense rows with identity, a few columns and a row menu RecordList Re-flows to a compact two-line row on a phone.
A whole index page with search, filters and paging AppList A page template: it holds a table or a RecordList in its children snippet.
Rows that arrive over time (SSE, WebSocket, a bulk job) LiveList Batched polite announcements, follow-the-tail that never steals focus. Needs label.
A very long list, or chat history that loads older items at the top VirtualList Windowed, keeps the scroll position when older rows load.
Events in time order Timeline Colored markers on a chronological list.
A list of rows the user picks one of (places, conversations, results) Button variant="ghost" tone="neutral" align="between" fullWidth with aria-pressed, in Stack as="ul" RecordList and RecordRow do not take a row click. Inside a table, TableRow onactivate. A chat thread list is ConversationItem.
Dense CRM-style list with pinned columns, row selection, groups and sort SimpleTable density="compact", stickyColumns={1}; rows are TableRow (selected, onactivate), sections TableGroupRow (collapsible, bind:open), headers TableSortHeader (sort, onsort).
Cards in columns (pipeline, status board) Kanban Drag between columns, keyboard “Move to…”, onmove, per-column onloadmore, tabs on a phone. Needs columns and getId.

Never: hand-write a <table> with custom sticky CSS, or a mobile card view next to a table. SimpleTable does both.

Container

If you need Use Why / the rule
A content section with a title and padding Card A Surface with a header, padding and an optional link. The default container. The title header is aligned with the content padding; gap="md" stacks the children in a column.
A tag or label on a card (a plan name, “New”, a category) Badge tone="brand" subtle Badge is a pill with a tone. Chip is for filters and selection, StatusChip for a record status.
A pricing table Grid of Card No pricing component: Grid columns={{ base: 1, md: 3 }}, the price in Heading size="display-3", features in Stack as="ul", class="ca-pricing-cta" on the Button. See Landing page.
One metric inside a panel, no box around it Stat Card-less: label, value, unit, hint, trend, trendTone, size, align, skeleton. KpiCard is a Stat inside a Card with an icon.
A painted panel with your own structure Surface Elevation, material, tone, variant and radius as independent props.
Spacing with no paint Box A div with padding props.
A page band with the page rhythm Section A <section>, size 1 to 3.
A centered, width-capped column Container size sm, md, lg, xl with a fluid gutter.

Layout primitives

All six render any element with as and take spacing as tokens, never inline style.

If you need Use Why / the rule
A row of items Flex direction, align, justify, wrap, gap.
A vertical stack of items Stack A Flex column: gap, align, justify.
Equal columns Grid columns is 1 to 12 or a responsive object. There is no cols prop.
A template like 2fr 1fr or repeat(auto-fill, minmax(200px, 1fr)) Grid plus a class Write grid-template-columns in a class with --ca-space-* gaps.
Padding only Box p, px, py.
A centered page column, a page band Container, Section See Container above.
Two or more regions the user sizes by dragging ResizablePanels ResizablePanel + ResizableHandle, sizes in percent, arrow-key resize, persist to keep the layout. A fixed split is Grid.
Slides or cards that scroll sideways, one or a few at a time Carousel CarouselItem children, label required, arrows and dots, perView, loop, autoplay (opt in, Pause button). A plain row that overflows is ScrollArea.
Every picture in a grid or a card cover in one shape Image ratio ratio={16 / 9} crops to the box (fit="contain" letterboxes) and keeps lazy loading and zero layout shift.
A video, map or iframe that must hold a ratio while it loads AspectRatio ratio; a direct iframe, video or img child fills the box. Layout only: no role.

gap, p, px and py take a spacing step as a string: gap="4" is --ca-space-4. A number such as gap={16} is the old API. Row is deprecated: use Flex. PageShell gap is the exception and takes a number in px.

<script>
  import { Container, Flex, Grid, Stack } from "@codeatlasdev/ui";
</script>

<Container size="md">
  <Stack gap="4">
    <Flex gap="2" align="center" justify="between">…</Flex>
    <Grid columns={{ base: 1, md: 3 }} gap="4">…</Grid>
  </Stack>
</Container>

Pick one of many

If you need Use Why / the rule
One value from a short list in a form Select Label, hint, error state, keyboard navigation.
One value from a long list, with search Combobox A Select with built-in filtering.
One value where every option should stay visible RadioGroup variant="list" or "cards".
A view or mode switch that always has a selection SegmentedControl Sliding thumb. Not for form answers.
A set of toggle buttons, single or multiple ToggleGroup type="single" or "multiple". Bind value as a string or an array.
Filter chips above a list FilterGroup Adds an automatic “All”. Single value.
One value from many short options that wrap ChipGroup Single choice, chips wrap to new rows, radiogroup semantics, bind:value. SegmentedControl is one row for 2 to 5 options; RadioGroup shows long labels; FilterGroup is a list toolbar with “All”.
Switch between panels of content Tabs Roving focus. Content changes, not a form value.
A yes or no that is submitted with the form Checkbox Also indeterminate.
A setting that applies as soon as it flips Switch role="switch". Use SettingItem for a labeled row.

Feedback

If you need Use Why / the rule
A short message after an action, gone in seconds toast Mount Toaster once, call toast.success() and friends from anywhere.
A message that stays in the page (validation, a warning) Alert severity, optional title and closable.
A decision the user must make AlertDialog See Overlay.
A change a screen reader should hear with no visual element announce() From @codeatlasdev/ui. Do not build your own live region.
A secondary line, one inline action, an update in place toast options description under the message, action: { label, onClick } (one button, dismisses), id to replace the toast with that id instead of stacking. Identical messages dedupe into one with a count. Max 3 visible, oldest dropped.
A list of past events with an unread count NotificationBell Bell with a badge that opens a notification center.
Known progress, or work of unknown length in a bar Progress value and max, or indeterminate. Needs label. size is sm (4px), md (6px) or lg (8px, default); another height: --ca-progress-height.
Placeholder for content that is loading Skeleton Match the shape of the real content.
A busy button, section or overlay Spinner Inline indicator.
A route or section that must not flash a loader LoadingBoundary Delays the loader, adds a top bar and pending links for SvelteKit navigations.

Buttons and type

If you need Use Why / the rule
A secondary action beside a brand filled one Button tone="neutral" Draws outlined and ghost in neutral label and border. Also on IconButton. Other variants ignore it.
Related actions that read as one control (range switcher, formatting toolbar, split button) ButtonGroup Attached Button/IconButton/DropdownMenu trigger, role="group", orientation. A choice that stays selected is SegmentedControl.
A field with fixed context or its own button inside the border (https://, $, a copy or Apply button) TextField prefix / suffix A string is text read with the field; a snippet is a Button/IconButton with its own name. leading/trailing are bare icons.
A hero or page title in the fluid editorial size Heading size="display" display-2 and display-3 are smaller steps. Plain h1 keeps the h1 size. PageShell, AuthCard, SiteHeader and EmptyState take display, which uses --ca-font-display.
A marker, a struck-out old value, a paper grain Highlight, StrikeThrough, Surface grain Editorial primitives. Highlight animated and StrikeThrough animated play once on mount.

Brazilian and formatted inputs

If you need Use Why / the rule
Free text TextField Label, helper, error, leading and trailing slots.
CPF, CNPJ, dates or any pattern MaskedField From @codeatlasdev/ui/forms. Live formatting.
Currency MoneyInput From @codeatlasdev/ui/forms. The bound value is an integer in minor units (cents). BRL by default.
A phone number PhoneField From @codeatlasdev/ui/forms. Binds E.164 while showing the mask.
A postal code with address lookup CepField From @codeatlasdev/ui/forms. Calls the onlookup you pass; the package makes no requests.
A date, a date range or a time DatePicker, DateField, TimeField Main import. Keyboard operable.
<script lang="ts">
  import { TextField } from "@codeatlasdev/ui";
  import { MoneyInput, PhoneField } from "@codeatlasdev/ui/forms";

  let name = $state("");
  let price = $state(0); // integer, minor unit
  let phone = $state(""); // E.164, empty while incomplete
</script>

<TextField label="Name" bind:value={name} />
<MoneyInput label="Price" bind:value={price} />
<PhoneField label="Phone" bind:value={phone} />

Setup path

If your app is Do this Why / the rule
SvelteKit 2 (^2.12) SvelteKit, then bunx @codeatlasdev/ui init --write init reads the Kit major and writes the right theme import.
SvelteKit 3 SvelteKit 3 Config goes into sveltekit({ ... }), $lib becomes #lib/*. init prints a note for what it will not rewrite.
Astro Astro Svelte components are islands: add a client:* directive.
Plain Vite and Svelte Plain Vite init does not support it: wire the CSS and the theme by hand.
Not sure what is wired bunx @codeatlasdev/ui init A dry run. It prints would create lines and diffs and writes nothing until --write.