Composable Svelte Components
This skill covers the UI component library for Composable Svelte applications.
For Specialized Components: See dedicated skills for graphics (3D), code (editors/media), charts (data viz), and maps (geospatial).
π¨ COMPLETE IMPORTS REFERENCE β ONLY THESE EXIST π¨
This is the authoritative list of every component exported by the library. If a component or subcomponent is not listed here, it DOES NOT EXIST. Do not invent components. Do not assume subcomponents exist based on other libraries.
From @composable-svelte/core/components/ui
// Buttons
import { Button } from '@composable-svelte/core/components/ui';
import { ButtonGroup } from '@composable-svelte/core/components/ui';
import { IconButton } from '@composable-svelte/core/components/ui';
// Form inputs
import { Input } from '@composable-svelte/core/components/ui';
import { Label } from '@composable-svelte/core/components/ui';
import { Textarea } from '@composable-svelte/core/components/ui';
import { Checkbox } from '@composable-svelte/core/components/ui';
import { Radio, RadioGroup } from '@composable-svelte/core/components/ui';
import { Switch } from '@composable-svelte/core/components/ui';
import { Slider } from '@composable-svelte/core/components/ui';
import { Select } from '@composable-svelte/core/components/ui'; // Single component β NO SelectTrigger/SelectContent/SelectItem
import { Combobox } from '@composable-svelte/core/components/ui';
// Display
import { Badge } from '@composable-svelte/core/components/ui';
import { Avatar } from '@composable-svelte/core/components/ui'; // Single component β NO AvatarImage/AvatarFallback
import { Skeleton } from '@composable-svelte/core/components/ui';
import { Progress } from '@composable-svelte/core/components/ui';
import { Spinner } from '@composable-svelte/core/components/ui';
import { Empty } from '@composable-svelte/core/components/ui';
// Card (HAS subcomponents)
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@composable-svelte/core/components/ui';
// Layout
import { Panel } from '@composable-svelte/core/components/ui';
import { Box } from '@composable-svelte/core/components/ui';
import { AspectRatio } from '@composable-svelte/core/components/ui';
import { Separator } from '@composable-svelte/core/components/ui';
// Typography
import { Heading } from '@composable-svelte/core/components/ui';
import { Text } from '@composable-svelte/core/components/ui';
import { Kbd } from '@composable-svelte/core/components/ui';
// Banner (HAS subcomponents)
import { Banner, BannerTitle, BannerDescription } from '@composable-svelte/core/components/ui';
// Breadcrumb (HAS subcomponents)
import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, BreadcrumbEllipsis } from '@composable-svelte/core/components/ui';
// Accordion (HAS subcomponents)
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@composable-svelte/core/components/ui';
// Collapsible (HAS subcomponents)
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@composable-svelte/core/components/ui';
// Overlays
import { Tooltip } from '@composable-svelte/core/components/ui';
import { DropdownMenu } from '@composable-svelte/core/components/ui';
// Other
import { Pagination } from '@composable-svelte/core/components/ui';
import { Calendar } from '@composable-svelte/core/components/ui';
import { Carousel } from '@composable-svelte/core/components/ui';
import { TreeView } from '@composable-svelte/core/components/ui';
import { FileUpload } from '@composable-svelte/core/components/ui';
From @composable-svelte/core/components/data-table
import { DataTable, DataTableHeader, DataTablePagination } from '@composable-svelte/core/components/data-table';
import { createTableReducer, createInitialState } from '@composable-svelte/core/components/data-table';
There is no Table component. For sorting/filtering/pagination use DataTable;
for a static list, plain HTML plus Tailwind is fine (see the DataTable section).
From @composable-svelte/core/components/form
import { Form, FormField, FormControl, FormItem, FormLabel, FormMessage, FormDescription } from '@composable-svelte/core/components/form';
import { createFormReducer, createInitialFormState } from '@composable-svelte/core/components/form';
From @composable-svelte/core/components/toast
import { Toaster, Toast, ToastTitle, ToastDescription, ToastAction,
toastReducer, createInitialToastState } from '@composable-svelte/core/components/toast';
From @composable-svelte/core/components/command
import { Command, CommandInput, CommandList, CommandGroup, CommandItem } from '@composable-svelte/core/components/command';
From @composable-svelte/core/components/image-gallery
import { ImageGallery, ImageLightbox } from '@composable-svelte/core/components/image-gallery';
Navigation Components (from @composable-svelte/core)
import { Modal } from '@composable-svelte/core';
import { Sheet } from '@composable-svelte/core';
import { Alert } from '@composable-svelte/core';
import { Drawer } from '@composable-svelte/core';
import { Popover } from '@composable-svelte/core';
import { Sidebar } from '@composable-svelte/core';
import { Tabs } from '@composable-svelte/core';
import { NavigationStack } from '@composable-svelte/core';
import { AnimatedNavigationStack } from '@composable-svelte/core';
β COMPONENTS THAT DO NOT EXIST
These are commonly hallucinated from other libraries. They are NOT available:
Table,TableHeader,TableRow,TableHead,TableBody,TableCellβ useDataTablefrom@composable-svelte/core/components/data-tableinsteadAvatarImage,AvatarFallbackβAvataris a single component withsrc,fallback, andaltpropsSelectTrigger,SelectContent,SelectItem,SelectValueβSelectis a single component withoptions,value, andonchangepropsTabsList,TabsTrigger,TabsContentβTabsis a single component from@composable-svelte/coreRadioGroupItemβ useRadiofrom theRadio, RadioGroupexportAlertTitle,AlertDescription,AlertActionsβAlertis a navigation component from@composable-svelte/core
COMPONENT LIBRARY OVERVIEW
Composable Svelte includes 77 components (68 high-level + 9 primitives) for building modern UIs. All components integrate with the Composable Architecture via props and state management.
Integration Pattern:
- Props for configuration (labels, variants, styles)
- State from
$store(subscription) or$derived(store.state)(rune-based) for reactive data - Dispatch actions for user interactions
Icons: Components use inline SVGs for structural icons (chevrons, arrows, checkmarks) β these are not currently configurable via props. For application-level icons, use lucide-svelte (the recommended icon library). Verify icon names against https://lucide.dev/icons/ β do NOT guess icon names.
import { ChevronDown, Plus, Search } from 'lucide-svelte';
Note: The 22 components with hardcoded inline SVGs (accordion chevron, breadcrumb separator, pagination arrows, etc.) cannot currently be swapped for custom icons. This is a known limitation.
Package Organization:
@composable-svelte/core- UI components (this skill)@composable-svelte/graphics- 3D graphics (see composable-svelte-graphics skill)@composable-svelte/code- Code editors, media players (see composable-svelte-code skill)@composable-svelte/charts- Data visualization (see composable-svelte-charts skill)@composable-svelte/maps- Interactive maps (see composable-svelte-maps skill)
π¨ STYLING SETUP β REQUIRED
These components ship no scoped CSS. Every visual style is a Tailwind utility
class (bg-popover, text-muted-foreground, border-border, β¦). If the consuming
app has no Tailwind pipeline, or Tailwind cannot resolve the theme tokens,
components render unstyled or transparent β see-through popovers, dropdowns
and select menus. This is the single most common integration bug.
Two things are required in the consuming app:
- Import the library stylesheet once, at the app entry.
- Let Tailwind scan the library, so its classes are not purged.
Tailwind v4 β one import does both:
@import 'tailwindcss';
@import '@composable-svelte/core/styles/tailwind.css';
Tailwind v3 β preset plus the exported content glob:
import composableSvelte, { contentGlob } from '@composable-svelte/core/tailwind-preset';
export default {
presets: [composableSvelte],
content: ['./src/**/*.{html,js,svelte,ts}', contentGlob]
};
@import '@composable-svelte/core/styles/globals.css';
contentGlob must be listed explicitly β Tailwind v3 does not merge a preset's
own content.
Do not hand-author --popover / --color-popover in app CSS to work around a
transparent component, and do not import both styles/globals.css and
styles/theme.css. Dark mode is the dark class on <html>.
π Full setup, theme overriding and troubleshooting: the "Styling & Theming"
section of packages/core/README.md. Read it before writing component code in a
new app rather than reconstructing it from memory.
NAVIGATION COMPONENTS
Purpose: Overlay-based UI elements for state-driven navigation.
Integration Pattern: State-driven open/close via store, dismiss via PresentationAction.
See composable-svelte-navigation skill for implementation details. This section provides REFERENCE only.
β οΈ These are NOT
open/onOpenChangecomponents. Each takes a framework-mintedPresentationView. They render nothing when that view is absent or retired and nopresentationis mid-flight β a non-idlepresentationkeeps them mounted so the exit animation can finish (ModalPrimitive.svelte:88-91). Any example showing<Modal open={true}>is wrong.
Shared props
Modal, Sheet, Drawer, Alert and Popover share one signature
(navigation-components/Modal.svelte:16-66):
store?: PresentationView<State, Action>β a managed presentation capability;undefinedmeans absent, and a retired view readsundefinedpresentation?: PresentationState<any>β omit for instant show/hideonPresentationComplete?: () => voidonDismissalComplete?: () => voidspringConfig?: Partial<SpringConfig>unstyled?: booleanclass?: stringbackdropClass?: stringβ Modal/Sheet/Drawer/Alert only (Popover has no backdrop)disableClickOutside?: booleandisableEscapeKey?: boolean
The children snippet receives { visible, store } β plus height on Sheet
(Sheet.svelte:151) and side, width on Drawer (Drawer.svelte:155).
Per-component extras:
| Component | Extra props |
|---|---|
| Sheet | side?: 'bottom' \| 'left' \| 'right' (default 'bottom'), height?: string (default '60vh') |
| Drawer | side?: 'left' \| 'right' (default 'left'), width?: string (default '320px') |
| Popover | style?: string β caller supplies absolute positioning |
| Alert | none |
| Modal | none |
import { Modal } from '@composable-svelte/core';
<Modal store={detailStore} ariaLabel="Product details">
{#if detailStore && detailStore.state !== undefined}
{@const detail = detailStore}
<ProductDetail store={detail} onBack={() => detail.dismiss()} />
{/if}
</Modal>
Receive detailStore from a managed feature view or bind an admitted optional/destination
slot through its managed composition. Its dismiss() requests closure of that exact
owner. Legacy scopeToDestination(...) stores are for reads and dispatch only and
are not valid presentation inputs. The managed feature view also supplies the
surface action for the actual feature content; see the application views guide.
This compact example uses instant show/hide; explicit presentation completion
props remain available for qualified rendering integrations.
Tabs
Props (navigation-components/Tabs.svelte:14-29):
store: ChildView<State, Action> | undefinedβ a managed read/dispatch view; absent or retired views render nothingtabs: string[]β plain strings, not{ value, label }objectsactiveTab: numberβ a numeric index, not a string keyonTabChange: (index: number) => voidunstyled?,tabListClass?,tabClass?,class?
<Tabs
store={scopedStore}
tabs={['Overview', 'Analytics', 'Reports']}
activeTab={store.state.activeTabIndex}
onTabChange={(index) => store.dispatch({ type: 'tabChanged', index })}
/>
See composable-svelte-navigation for the full navigation patterns β scoping, presentation lifecycle and the destination reducers.
FORM COMPONENTS
Purpose: User input elements that integrate with Composable Architecture.
Integration Pattern: Value from $store (subscription) or $derived(store.state) (rune-based), dispatch on change, validation state from store.
See composable-svelte-forms skill for full patterns.
Input
Text input field with variants.
Types (components/ui/input/Input.svelte:46): 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search'. date and time render fine at runtime but
are outside the declared union, so they fail svelte-check β use Calendar or a
plain <input type="date">.
Props:
type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search'value?: string | numberβ coerced to a number whentype="number"(Input.svelte:51)oninput?: (e: Event & { currentTarget: HTMLInputElement }) => voidplaceholder: string- Placeholder textdisabled: boolean- Disabled state
import { Input } from '@composable-svelte/core/components/ui';
<Input
type="text"
value={$store.name}
oninput={(e) => store.dispatch({ type: 'nameChanged', name: e.currentTarget.value })}
placeholder="Enter name"
disabled={$store.isSubmitting}
/>
{#if $store.nameError}
<span class="error">{$store.nameError}</span>
{/if}
β οΈ None of these take an
onValueChange/onCheckedChangecallback. Those props do not exist and are silently ignored. See composable-svelte-forms β INPUT COMPONENT WIRING REFERENCE for the same table in a form context.
Select
Dropdown selector.
Props (components/ui/select/Select.svelte):
options: SelectOption<T>[]β{ value, label, disabled?, description? }value?: T | T[] | nullβ$bindableonchange?: (value: T | T[] | null) => voidβ notonValueChangeplaceholder?,searchable?,multiple?,disabled?,class?
import { Select } from '@composable-svelte/core/components/ui';
<Select
value={store.state.category}
options={[
{ label: 'Electronics', value: 'electronics' },
{ label: 'Clothing', value: 'clothing' }
]}
onchange={(value) => store.dispatch({ type: 'categoryChanged', category: value })}
placeholder="Select category"
/>
Checkbox
Boolean toggle. It accepts no children (no children prop, nothing rendered
for one), so the label must be supplied by the caller.
Props (components/ui/checkbox/Checkbox.svelte:25-37):
checked?: booleanβ$bindableindeterminate?: boolean,disabled?: boolean,class?: string- plus arbitrary
<input>attributes via restProps (this is how you getonchange)
<label class="flex items-center gap-2">
<Checkbox
checked={store.state.agreeToTerms}
onchange={(e) => store.dispatch({ type: 'toggleTerms', checked: e.currentTarget.checked })}
/>
<span>I agree to the terms and conditions</span>
</label>
RadioGroup
Mutually exclusive options. No callback prop exists β RadioGroup declares no
restProps, so anything you pass beyond the props below is dropped. Selection is
captured by an internal handler that assigns to the bindable value.
Props (components/ui/radio/RadioGroup.svelte:23-35):
value?: string | nullβ$bindable; the only way to read the selectionname?: string,class?: string,children
Radio takes value: string, disabled?, class?, children, and throws if used
outside a RadioGroup.
<script lang="ts">
let plan = $state(store.state.plan);
$effect(() => store.dispatch({ type: 'planChanged', plan }));
</script>
<RadioGroup bind:value={plan}>
<Radio value="free">Free</Radio>
<Radio value="pro">Pro ($9/mo)</Radio>
</RadioGroup>
If you want a pure dispatch flow with no local mirror, use native
<input type="radio"> instead.
Switch
Toggle switch.
Props (components/ui/switch/Switch.svelte:27, $bindable at :43):
checked?: booleanβ$bindabledisabled?,class?, plus restProps spread onto the<button>
There is no onCheckedChange. The component defines an internal onclick toggle,
but restProps are spread after it, so a caller-supplied onclick replaces that
handler β which is what makes one-way checked + onclick keep the store as the
single source of truth.
<Switch
checked={store.state.notifications}
onclick={() => store.dispatch({ type: 'notificationsToggled' })}
/>
Textarea
Multi-line text input.
Props: value, oninput, rows, placeholder.
Combobox
Autocomplete dropdown.
Props (components/ui/combobox/Combobox.svelte):
options?: ComboboxOption<T>[]value?: T | nullβ$bindableonchange?: (value: T | null) => voidβ notonValueChangeloadOptions?: (query: string) => Promise<ComboboxOption<T>[]>β async mode; notonSearchChangedebounceDelay?: number(default 300),placeholder?,disabled?,class?
DATA DISPLAY COMPONENTS
Purpose: Display data from store.state, often derived/computed.
Integration Pattern: Map from store.state arrays, use $derived for filtering/sorting.
DataTable
Tabular data display with sorting/filtering/pagination.
When to use: Lists of structured data, data grids.
There is no
Tablecomponent, andDataTablehas nodataorcolumnsprop. It is store-driven;columnsbelongs toDataTableHeader.
Props (components/data-table/DataTable.svelte:15-50):
store: Store<TableState<T>, TableAction<T>>β requiredrow: Snippet<[T]>β requiredheader?: Snippet,footer?: SnippetemptyMessage?,loadingMessage?,class?,tableClass?
<script lang="ts">
import { createStore } from '@composable-svelte/core';
import { DataTable, DataTableHeader, DataTablePagination,
createTableReducer, createInitialState }
from '@composable-svelte/core/components/data-table';
const store = createStore({
initialState: createInitialState<User>({ initialData: users, pageSize: 10 }),
reducer: createTableReducer<User>()
});
</script>
<DataTable {store}>
{#snippet header()}
<DataTableHeader {store} columns={[
{ key: 'name', label: 'Name', sortable: true },
{ key: 'email', label: 'Email' }
]} />
{/snippet}
{#snippet row(user: User)}
<tr class="border-b">
<td class="p-4">{user.name}</td>
<td class="p-4">{user.email}</td>
</tr>
{/snippet}
</DataTable>
<DataTablePagination {store} pageSizeOptions={[10, 25, 50]} />
Working reference: examples/data-table/src/App.svelte.
For simple tables with no sorting, filtering or pagination, plain HTML plus Tailwind is lighter and perfectly acceptable:
<table class="w-full text-sm">
<thead>
<tr class="border-b text-left text-muted-foreground">
<th class="px-4 py-3 font-medium">Name</th>
<th class="px-4 py-3 font-medium">Email</th>
<th class="px-4 py-3 font-medium">Status</th>
</tr>
</thead>
<tbody>
{#each $store.users as user (user.id)}
<tr class="border-b hover:bg-muted/50">
<td class="px-4 py-3">{user.name}</td>
<td class="px-4 py-3">{user.email}</td>
<td class="px-4 py-3">{user.status}</td>
</tr>
{/each}
</tbody>
</table>
Card
Container for related content with header/footer.
When to use: Product cards, user profiles, content previews.
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@composable-svelte/core/components/ui';
{#each $store.products as product (product.id)}
<Card>
<CardHeader>
<CardTitle>{product.name}</CardTitle>
<CardDescription>{product.category}</CardDescription>
</CardHeader>
<CardContent>
<p>${product.price}</p>
<Button onclick={() => store.dispatch({ type: 'addToCart', productId: product.id })}>
Add to Cart
</Button>
</CardContent>
</Card>
{/each}
Badge
Small label/tag.
Variants (components/ui/badge/Badge.svelte:5): default, secondary, destructive, outline, success, warning.
import { Badge } from '@composable-svelte/core/components/ui';
<Badge variant={$store.status === 'active' ? 'success' : 'secondary'}>
{$store.status}
</Badge>
Avatar
User profile image with fallback.
import { Avatar } from '@composable-svelte/core/components/ui';
<Avatar
src={$store.user?.avatarUrl}
alt={$store.user?.name}
fallback={$store.user?.initials}
/>
FEEDBACK COMPONENTS
Purpose: Communicate loading states, errors, and notifications.
Integration Pattern: Render based on loading/error/success state from store.
Toast
Temporary notification.
When to use: Success messages, errors, notifications.
There is no imperative
toast.success()/toast.error()API β the package exports no such object. Toasts are reducer-driven: composetoastReducerinto your state and dispatchtoastAdded.
<script lang="ts">
import { Toaster } from '@composable-svelte/core/components/toast';
</script>
<!-- Controlled: you own the state, you render it -->
<Toaster toasts={store.state.toasts.toasts} position="bottom-right" />
// In the reducer β toastReducer is composed in via scope()
case 'itemAdded':
return [
{ ...state, items: [...state.items, action.item] },
Effect.none()
];
// From a component or effect
store.dispatch({
type: 'toasts',
action: { type: 'toastAdded', toast: { variant: 'success', description: 'Item added' } }
});
Available toast actions: toastAdded, toastDismissed, toastAutoDismissed,
toastActionClicked, allToastsDismissed, maxToastsChanged,
defaultDurationChanged, positionChanged.
β οΈ When you pass the
toastsprop,Toaster's own dismiss button dispatches to its internal store and has no visible effect β auto-dismiss must come from your composedtoastReducer(which emitsEffect.afterDelayβtoastAutoDismissed). Rendering<Toaster />with notoastsprop uses an internal store you cannot dispatch to.
Progress
Linear progress indicator.
When to use: Upload progress, loading progress.
import { Progress } from '@composable-svelte/core/components/ui';
{#if $store.uploadProgress !== null}
<Progress value={$store.uploadProgress} max={100} />
<p>{$store.uploadProgress}% uploaded</p>
{/if}
Skeleton
Loading placeholder with a pulse animation (animate-pulse).
When to use: Content placeholders during loading.
import { Skeleton } from '@composable-svelte/core/components/ui';
{#if $store.isLoading}
<Skeleton class="h-4 w-full mb-2" />
<Skeleton class="h-4 w-3/4 mb-2" />
<Skeleton class="h-4 w-1/2" />
{:else}
<p>{$store.content}</p>
{/if}
Spinner
Loading spinner.
Props (components/ui/spinner/Spinner.svelte:22):
size?: 'sm' | 'md' | 'lg'(default'md') β'small'/'medium'/'large'are not valid and produce an unsized spinnerclass?: string
import { Spinner } from '@composable-svelte/core/components/ui';
{#if store.state.isLoading}
<Spinner size="lg" />
{/if}
LAYOUT COMPONENTS
Purpose: Organize UI with expand/collapse, tabs, resizable panels.
Integration Pattern: Expanded/active state lives in store, dispatch on user interaction.
Accordion
Expandable/collapsible sections.
When to use: FAQs, collapsible content sections.
Accordionowns its expansion state internally β it callscreateStore()itself (components/ui/accordion/Accordion.svelte:92). Do not try to drive it from your store; observe it withonExpand/onCollapseinstead.
Props (Accordion.svelte:47-77):
items?: AccordionItem[],initialExpandedIds?: string[]allowMultiple?: boolean,collapsible?: booleanonExpand?: (id: string) => void,onCollapse?: (id: string) => voidclass?: string
AccordionItem takes id: string (not value), disabled?, class?.
AccordionTrigger takes only class? plus children β no onclick, no expanded.
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
from '@composable-svelte/core/components/ui';
<Accordion
initialExpandedIds={['faq-1']}
allowMultiple
onExpand={(id) => store.dispatch({ type: 'faqExpanded', id })}
>
{#each store.state.faqItems as item (item.id)}
<AccordionItem id={item.id}>
<AccordionTrigger>{item.question}</AccordionTrigger>
<AccordionContent>{item.answer}</AccordionContent>
</AccordionItem>
{/each}
</Accordion>
Collapsible
π« Currently unusable from a consumer app.
Collapsiblerequires astore: Store<CollapsibleState, CollapsibleAction>, butcollapsibleReducerandcreateInitialCollapsibleStateare not re-exported from any public entry point βcomponents/ui/index.tsexposes only the three Svelte components, so there is no way to construct the store it needs. It has noopen/onOpenChangeprops.Use
Accordionwith a single item, or plain markup, until the reducer is exported.
SPECIALIZED COMPONENT PACKAGES
For specialized components beyond standard UI, see dedicated skills:
3D Graphics
Skill: composable-svelte-graphics
Package: @composable-svelte/graphics
Components: Scene, Camera, Light, Mesh
Use cases: 3D visualizations, WebGL rendering, geometry (box, sphere, cylinder, torus, plane, custom)
Code & Media
Skill: composable-svelte-code
Package: @composable-svelte/code
Components: CodeEditor, CodeHighlight, AudioPlayer, VideoEmbed, VoiceInput, NodeCanvas, StreamingChat
Use cases: Code editing, syntax highlighting, media playback, voice recognition, visual programming, chat interfaces
Charts & Data Visualization
Skill: composable-svelte-charts
Package: @composable-svelte/charts
Components: Chart, ChartPrimitive
Use cases: Data visualization, interactive charts, statistical plots
Maps & Geospatial
Skill: composable-svelte-maps
Package: @composable-svelte/maps
Components: Map, MapPrimitive, GeoJSONLayer, HeatmapLayer, Popup, TileProviderControl
Use cases: Interactive maps, geospatial data, location-based features
COMPONENT SELECTION DECISION TREE
Navigation Components
What kind of overlay?
β
ββ Full-screen important action β Modal
ββ Bottom panel (mobile-first) β Sheet
ββ Side panel (navigation/settings) β Drawer
ββ Quick confirmation (yes/no) β Alert
ββ Contextual menu (dropdown) β Popover
Form Components
What kind of input?
β
ββ Single line text β Input
ββ Multi-line text β Textarea
ββ Boolean toggle β Checkbox or Switch
ββ One from many options β RadioGroup or Select
ββ Autocomplete/search β Combobox
ββ Date/time β Calendar, or a plain <input type="date"> (Input's type union excludes date/time)
Data Display
What kind of data?
β
ββ Tabular data β DataTable
ββ List of items β Cards or List
ββ Status/label β Badge
ββ User profile β Avatar
ββ Metrics/stats β Card with metrics
Feedback
What kind of feedback?
β
ββ Loading state β Spinner or Skeleton
ββ Progress indicator β Progress
ββ Success/error notification β Toast
ββ Confirmation needed β Alert
CUSTOM COMPONENT GUIDELINES
Principles for Building Custom Components:
- No
$statefor Application State: All state that affects behavior or can be tested must be in the store - Dispatch Actions: User interactions dispatch actions to the store
- Read from Store: Render based on
$store(subscription) or$derived(store.state)(rune-based) - Use
$derived: For computed values derived from store state - Props for Configuration: Static configuration (labels, styles) can be props
Example Custom Component:
<script lang="ts">
import type { Store } from '@composable-svelte/core';
// Runes mode: $props(), not `export let`. Mixing `export let` with $derived
// is a compile error.
let {
store,
label,
variant = 'primary'
}: {
store: Store<State, Action>;
label: string;
variant?: 'primary' | 'secondary';
} = $props();
const isDisabled = $derived(store.state.isLoading || store.state.hasErrors);
const displayText = $derived(
store.state.count > 0 ? `${label} (${store.state.count})` : label
);
</script>
<button
class={variant}
disabled={isDisabled}
onclick={() => store.dispatch({ type: 'buttonClicked' })}
>
{displayText}
</button>
ACCESSIBILITY PATTERNS
Keyboard Navigation
All interactive components support keyboard navigation:
- Tab: Move focus between elements
- Enter/Space: Activate buttons, toggles
- Escape: Close modals, popovers, dropdowns
- Arrow keys: Navigate lists, select options
Screen Reader Support
Components include ARIA attributes:
aria-label: Descriptive labelsaria-expanded: Expanded/collapsed statearia-selected: Selected staterole: Semantic roles
Focus Management
Components manage focus:
- Modal traps focus inside dialog
- Popover returns focus to trigger on close
- Forms focus first invalid field on submit
STYLING PATTERNS
Tailwind Integration
All components use Tailwind CSS classes. Most (51 of them) merge the class prop
with their defaults through cn() β tailwind-merge, so a conflicting class of
yours wins. A few β Carousel and FileUpload β concatenate the string instead,
so both classes survive and CSS source order decides; use an explicit override or
!important there if a conflict matters.
<Button class="bg-primary text-primary-foreground hover:bg-primary/90">
Click me
</Button>
Setup (required, and the cause of transparent components when missed) is in STYLING SETUP above.
Custom Styles
Override with custom CSS:
<Card class="custom-card">
<CardContent>
...
</CardContent>
</Card>
<style>
.custom-card {
background: linear-gradient(to right, #667eea 0%, #764ba2 100%);
}
</style>
SUMMARY
This skill covers the component library for Composable Svelte:
- Navigation Components: Modal, Sheet, Drawer, Alert, Popover, Tabs
- Form Components: Input, Select, Checkbox, RadioGroup, Switch, Textarea, Combobox
- Data Display: DataTable, Card, Badge, Avatar
- Feedback: Toast, Progress, Skeleton, Spinner
- Layout: Accordion (Collapsible is not currently usable β see its section)
- 3D Graphics: Scene, Camera, Light, Mesh (box, sphere, cylinder, torus, plane, custom)
- Component Selection: Decision trees for choosing components
- Custom Components: Guidelines for building custom components
- Accessibility: Keyboard, screen reader, focus management
- Styling: Tailwind is REQUIRED β see STYLING SETUP
Remember: Props for config, state in store, dispatch for interactions.
The five corrections most worth carrying:
Select/Comboboxuseonchange, neveronValueChangeCheckbox/Switch/RadioGroupexpose$bindablestate, not a change callback (Checkbox takes nativeonchangevia restProps, Switch takesonclick; RadioGroup takes neither)- Navigation components take
store, neveropen/onOpenChange Tabstakestabs: string[]and a numericactiveTab- There is no
Tablecomponent and no imperativetoast.*API
For navigation implementation, see composable-svelte-navigation skill. For form integration, see composable-svelte-forms skill. For core architecture, see composable-svelte-core skill. For testing components, see composable-svelte-testing skill.