Theme
← Examples
Advanced Forms
A composed form surface combining comboboxes, date picking, uploads, color selection, tags, and ratings.
apps/docs/src/lib/demos/AdvancedFormsDemo.svelte
Preview
Open in new tabAdvanced Forms
Combobox
Apple
Banana
Cherry
Date
Elderberry
Fig
Grape
Honeydew
Kiwi
Lemon
Mango
Nectarine
Orange
Papaya
Quince
DatePicker
March 2026
For typed date entry, use DateField instead of DatePicker.
PIN Input
Tags Input
Svelte TypeScript
Tags: Svelte, TypeScript
Rating
File Upload
Drag & drop files here
Color Picker
Color: #3b82f6 (alpha: 1.00)
Source
This page is rendered from the real demo file shown below.
svelte
<script lang="ts">
import {
Combobox,
DatePicker,
FileUpload,
ColorPicker,
PinInput,
TagsInput,
Rating,
} from '@dryui/ui';
// Combobox state
let comboboxValue = $state('');
let comboboxOpen = $state(false);
let comboboxInput = $state('');
const fruits = [
'Apple', 'Banana', 'Cherry', 'Date', 'Elderberry',
'Fig', 'Grape', 'Honeydew', 'Kiwi', 'Lemon',
'Mango', 'Nectarine', 'Orange', 'Papaya', 'Quince',
];
let filteredFruits = $derived(
comboboxInput
? fruits.filter((f) => f.toLowerCase().includes(comboboxInput.toLowerCase()))
: fruits,
);
// DatePicker state
let dateValue: Date | null = $state(null);
let dateOpen = $state(false);
// FileUpload state
let uploadedFiles: File[] = $state([]);
// ColorPicker state
let colorValue = $state('#3b82f6');
let colorAlpha = $state(1);
// PINInput state
let pinValue = $state('');
// TagsInput state
let tags: string[] = $state(['Svelte', 'TypeScript']);
// Rating state
let ratingValue = $state(3);
let halfRatingValue = $state(3.5);
function formatFileSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / 1048576).toFixed(1)} MB`;
}
</script>
<h2>Advanced Forms</h2>
<div class="demo-grid">
<!-- Combobox -->
<div class="demo-section">
<h3>Combobox</h3>
<Combobox.Root bind:value={comboboxValue} bind:open={comboboxOpen} name="framework">
<Combobox.Input placeholder="Search frameworks..." oninput={(e: Event) => {
comboboxInput = (e.target as HTMLInputElement).value;
}} />
<Combobox.Content>
{#each filteredFruits as fruit, index (fruit)}
<Combobox.Item value={fruit} {index}>{fruit}</Combobox.Item>
{/each}
{#if filteredFruits.length === 0}
<Combobox.Empty>No frameworks found</Combobox.Empty>
{/if}
</Combobox.Content>
</Combobox.Root>
{#if comboboxValue}
<p class="value-display">Selected: {comboboxValue}</p>
{/if}
</div>
<!-- DatePicker -->
<div class="demo-section">
<h3>DatePicker</h3>
<DatePicker.Root bind:value={dateValue} bind:open={dateOpen}>
<DatePicker.Trigger placeholder="Select a date..." />
<DatePicker.Content>
<DatePicker.Calendar />
</DatePicker.Content>
</DatePicker.Root>
{#if dateValue}
<p class="value-display">Selected: {dateValue.toLocaleDateString()}</p>
{/if}
<p class="value-display">For typed date entry, use DateField instead of DatePicker.</p>
</div>
<!-- PINInput -->
<div class="demo-section">
<h3>PIN Input</h3>
<div class="stack-sm">
<PinInput.Root
bind:value={pinValue}
length={6}
oncomplete={(v) => console.log('PIN complete:', v)}
/>
<PinInput.Root bind:value={pinValue} length={4} mask size="lg" />
</div>
{#if pinValue}
<p class="value-display">Value: {pinValue}</p>
{/if}
</div>
<!-- TagsInput -->
<div class="demo-section">
<h3>Tags Input</h3>
<TagsInput.Root bind:value={tags} maxTags={8}>
<TagsInput.List>
{#each tags as tag, i (tag)}
<TagsInput.Tag index={i} value={tag}>
{tag}
<TagsInput.TagDelete index={i} />
</TagsInput.Tag>
{/each}
</TagsInput.List>
<TagsInput.Input placeholder="Add tag..." />
</TagsInput.Root>
<p class="value-display">Tags: {tags.join(', ')}</p>
</div>
<!-- Rating -->
<div class="demo-section">
<h3>Rating</h3>
<div class="stack-sm">
<div class="rating-row">
<span class="rating-label">Standard:</span>
<Rating bind:value={ratingValue} />
<span class="value-display">{ratingValue}/5</span>
</div>
<div class="rating-row">
<span class="rating-label">Half stars:</span>
<Rating bind:value={halfRatingValue} allowHalf />
<span class="value-display">{halfRatingValue}/5</span>
</div>
<div class="rating-row">
<span class="rating-label">Readonly:</span>
<Rating value={4} readonly />
</div>
<div class="rating-row">
<span class="rating-label">Sizes:</span>
<Rating value={3} size="sm" readonly />
<Rating value={3} size="md" readonly />
<Rating value={3} size="lg" readonly />
</div>
</div>
</div>
<!-- FileUpload -->
<div class="demo-section">
<h3>File Upload</h3>
<FileUpload.Root bind:files={uploadedFiles} multiple accept="image/*,.pdf" maxSize={5242880}>
<FileUpload.Dropzone>
<div class="upload-content">
<p>Drag & drop files here</p>
<FileUpload.Trigger>Browse files</FileUpload.Trigger>
</div>
</FileUpload.Dropzone>
{#if uploadedFiles.length > 0}
<FileUpload.List>
{#snippet children({ file, index }: { file: File; index: number })}
<FileUpload.Item {file} {index}>
<span>{file.name} ({formatFileSize(file.size)})</span>
<FileUpload.ItemDelete {index} />
</FileUpload.Item>
{/snippet}
</FileUpload.List>
{/if}
</FileUpload.Root>
</div>
<!-- ColorPicker -->
<div class="demo-section">
<h3>Color Picker</h3>
<ColorPicker.Root bind:value={colorValue} bind:alpha={colorAlpha}>
<ColorPicker.Area />
<ColorPicker.HueSlider />
<ColorPicker.AlphaSlider />
<div class="color-row">
<ColorPicker.Swatch />
<ColorPicker.Input format="hex" />
<ColorPicker.EyeDropper />
</div>
<div class="color-presets">
<ColorPicker.Swatch color="#ef4444" />
<ColorPicker.Swatch color="#f59e0b" />
<ColorPicker.Swatch color="#22c55e" />
<ColorPicker.Swatch color="#3b82f6" />
<ColorPicker.Swatch color="#8b5cf6" />
<ColorPicker.Swatch color="#ec4899" />
</div>
</ColorPicker.Root>
<p class="value-display">Color: {colorValue} (alpha: {colorAlpha.toFixed(2)})</p>
</div>
</div>
<style>
.demo-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
}
.demo-section {
padding: 1.5rem;
border: 1px solid var(--dry-color-border);
border-radius: var(--dry-radius-lg);
background: var(--dry-color-surface);
}
.demo-section h3 {
margin: 0 0 1rem;
}
.stack-sm {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.value-display {
margin-top: 0.5rem;
font-size: var(--dry-text-sm-size);
color: var(--dry-color-muted);
}
.upload-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.upload-content p {
margin: 0;
color: var(--dry-color-muted);
}
.color-row {
display: flex;
gap: 0.5rem;
align-items: center;
margin-top: 0.5rem;
}
.color-presets {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
}
.rating-label {
font-size: var(--dry-text-sm-size);
color: var(--dry-color-muted);
white-space: nowrap;
}
.rating-row {
display: flex;
align-items: center;
gap: 0.25rem;
}
</style>