Skip to content

Basic Components

The basic UI components are the building blocks of applications. They come from the shadcn-svelte library and provide a consistent appearance.

Button component with various variants and sizes.

<script>
import { Button } from '$lib/components/ui/button';
</script>
<Button>Default</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
<Button size="sm">Small</Button>
<Button size="default">Default</Button>
<Button size="lg">Large</Button>
<Button size="icon">
<User size={16} />
</Button>
VariantUsageColor
defaultPrimary actionBlue
destructiveDangerous action (delete)Red
outlineSecondary actionOutlined
secondaryTertiary actionGray
ghostMinimal emphasisNo background
linkLink styleUnderlined
PropTypeDescription
variantstringButton variant (default: “default”)
sizestringButton size (default: “default”)
disabledbooleanDisabled state
onclick() => voidClick event

Delete button:

<Button variant="destructive" onclick={handleDelete}>
<Trash2 size={16} class="mr-2" />
Delete
</Button>

Loading state:

<script>
let loading = $state(false);
</script>
<Button disabled={loading} onclick={handleSave}>
{#if loading}
Saving...
{:else}
Save
{/if}
</Button>

Text input field component.

<script>
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
let value = $state('');
</script>
<div class="space-y-2">
<Label for="email">Email</Label>
<Input
id="email"
type="email"
placeholder="example@email.com"
bind:value
/>
</div>
<Input type="text" placeholder="Text" />
<Input type="email" placeholder="Email" />
<Input type="password" placeholder="Password" />
<Input type="number" placeholder="Number" />
<Input type="tel" placeholder="Phone" />
<Input type="url" placeholder="URL" />
<Input type="date" />
PropTypeDescription
typestringInput type (default: “text”)
placeholderstringPlaceholder text
valuestringValue (bindable)
disabledbooleanDisabled state
requiredbooleanRequired field

With validation:

<script>
let email = $state('');
let error = $state('');
function validate() {
if (!email.includes('@')) {
error = 'Invalid email address';
} else {
error = '';
}
}
</script>
<div class="space-y-2">
<Label for="email">Email *</Label>
<Input
id="email"
type="email"
bind:value={email}
oninput={validate}
class={error ? 'border-destructive' : ''}
/>
{#if error}
<p class="text-sm text-destructive">{error}</p>
{/if}
</div>

Dropdown menu component.

<script>
import * as Select from '$lib/components/ui/select';
import { Label } from '$lib/components/ui/label';
let selected = $state('');
</script>
<div class="space-y-2">
<Label>Role</Label>
<Select.Root bind:value={selected}>
<Select.Trigger>
<Select.Value placeholder="Select a role..." />
</Select.Trigger>
<Select.Content>
<Select.Item value="admin">Admin</Select.Item>
<Select.Item value="user">User</Select.Item>
<Select.Item value="guest">Guest</Select.Item>
</Select.Content>
</Select.Root>
</div>
<Select.Root bind:value={selected}>
<Select.Trigger>
<Select.Value placeholder="Select..." />
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.Label>Administrators</Select.Label>
<Select.Item value="superadmin">Super Admin</Select.Item>
<Select.Item value="admin">Admin</Select.Item>
</Select.Group>
<Select.Separator />
<Select.Group>
<Select.Label>Users</Select.Label>
<Select.Item value="user">User</Select.Item>
<Select.Item value="guest">Guest</Select.Item>
</Select.Group>
</Select.Content>
</Select.Root>

Checkbox component.

<script>
import { Checkbox } from '$lib/components/ui/checkbox';
import { Label } from '$lib/components/ui/label';
let checked = $state(false);
</script>
<div class="flex items-center space-x-2">
<Checkbox id="terms" bind:checked />
<Label for="terms">I accept the terms</Label>
</div>
<script>
let permissions = $state({
read: false,
write: false,
delete: false
});
</script>
<div class="space-y-2">
<div class="flex items-center space-x-2">
<Checkbox id="read" bind:checked={permissions.read} />
<Label for="read">Read</Label>
</div>
<div class="flex items-center space-x-2">
<Checkbox id="write" bind:checked={permissions.write} />
<Label for="write">Write</Label>
</div>
<div class="flex items-center space-x-2">
<Checkbox id="delete" bind:checked={permissions.delete} />
<Label for="delete">Delete</Label>
</div>
</div>

Toggle switch component for on/off state.

<script>
import { Switch } from '$lib/components/ui/switch';
import { Label } from '$lib/components/ui/label';
let enabled = $state(false);
</script>
<div class="flex items-center space-x-2">
<Switch id="notifications" bind:checked={enabled} />
<Label for="notifications">Enable notifications</Label>
</div>
<div class="flex items-center space-x-2">
<Switch id="feature" disabled />
<Label for="feature" class="text-muted-foreground">
Coming soon
</Label>
</div>

Label component for input fields.

<script>
import { Label } from '$lib/components/ui/label';
import { Input } from '$lib/components/ui/input';
</script>
<div class="space-y-2">
<Label for="username">Username *</Label>
<Input id="username" required />
</div>
<Label for="email">
Email <span class="text-destructive">*</span>
</Label>

Label component for indicating statuses and categories.

<script>
import { Badge } from '$lib/components/ui/badge';
</script>
<Badge>Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>
<script>
function getStatusBadge(status: string) {
switch (status) {
case 'active':
return { variant: 'default', label: 'Active' };
case 'inactive':
return { variant: 'secondary', label: 'Inactive' };
case 'error':
return { variant: 'destructive', label: 'Error' };
default:
return { variant: 'outline', label: 'Unknown' };
}
}
</script>
{#each users as user}
{@const badge = getStatusBadge(user.status)}
<Badge variant={badge.variant}>{badge.label}</Badge>
{/each}

Divider line component.

<script>
import { Separator } from '$lib/components/ui/separator';
</script>
<div class="space-y-4">
<div>First section</div>
<Separator />
<div>Second section</div>
</div>
<div class="flex items-center space-x-4">
<span>Item 1</span>
<Separator orientation="vertical" class="h-4" />
<span>Item 2</span>
</div>

  1. Always use Label — Every input should have a label
  2. Required field indicators — Use * or “Required” text
  3. Placeholder text — Give an example of the expected format
  4. Validation — Display errors below the input
  5. Disabled state — Use the disabled prop, not CSS
  6. Button variantsdestructive only for deletion, default for primary action
  7. Badge colors — Use semantic colors (red = error, green = success)
  8. Accessibility — Use id and for attributes with Label