Form Layouts
blocklayoutStructured create/edit entity form ("New project"): stacked SectionCards for project details (name with live slug preview, description with char counter), configuration (visibility Select, start date, budget), and access (link vs restricted RadioGroup, notification/comment Switch rows) with a sticky-feel footer bar. Create stays disabled until the name is filled; validation hints appear under empty required fields after the first submit attempt.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/form-layouts.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/form-layouts.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/form-layouts.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/form-layouts.jsonManifest:
https://uipkge.dev/r/svelte/form-layouts.jsonInstalls to:src/lib/components/blocks/form-layouts/Variants
Loading interactive previews…
Loading interactive previews…
npm dependencies
Files installed (1)
src/lib/components/blocks/form-layouts/FormLayouts.svelte6.9 kB
<script lang="ts" module> export interface FormLayoutsProps { class?: string } </script> <script lang="ts"> import { Globe, Link2, Lock } from '@lucide/svelte' import { Button } from '$lib/components/ui/button' import { Input } from '$lib/components/ui/input' import { Label } from '$lib/components/ui/label' import { RadioGroup, RadioGroupItem } from '$lib/components/ui/radio-group' import { SectionCard } from '$lib/components/ui/section-card' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '$lib/components/ui/select' import { Switch } from '$lib/components/ui/switch' import { Textarea } from '$lib/components/ui/textarea' let { class: className }: FormLayoutsProps = $props() const DESCRIPTION_LIMIT = 280 let name = $state('') let description = $state('') let visibility = $state('internal') let startDate = $state('') let budget = $state('') let access = $state<'link' | 'restricted'>('link') let notifications = $state(true) let comments = $state(true) let submitted = $state(false) const slug = $derived.by(() => { const cleaned = name .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') return cleaned || 'your-project' }) const showNameError = $derived(submitted && name.trim() === '') function handleSubmit() { submitted = true if (name.trim() === '') return } </script> <div data-slot="form-layouts" class={['max-w-2xl', className]}> <form class="space-y-6" novalidate onsubmit={(e) => { e.preventDefault() handleSubmit() }} > <SectionCard title="Project details" description="Name your project and describe what it covers."> <div class="space-y-4"> <div class="space-y-2"> <Label for="fl-name">Name</Label> <Input id="fl-name" bind:value={name} placeholder="Q3 launch site" autocomplete="off" aria-invalid={showNameError || undefined} /> {#if showNameError} <p role="alert" class="text-destructive text-xs">Project name is required.</p> {/if} </div> <div class="border-border bg-muted/40 flex items-center gap-2 rounded-md border px-3 py-2"> <Globe class="text-muted-foreground size-4 shrink-0" aria-hidden="true" /> <p class="min-w-0 truncate text-xs"> <span class="text-muted-foreground">acme.uipkge.app/</span> <span class="text-foreground font-medium">{slug}</span> </p> </div> <div class="space-y-2"> <Label for="fl-description">Description</Label> <Textarea id="fl-description" bind:value={description} rows={3} placeholder="What is this project about?" /> <p class="text-right text-xs {description.length > DESCRIPTION_LIMIT ? 'text-destructive' : 'text-muted-foreground'}" > {description.length}/{DESCRIPTION_LIMIT} </p> </div> </div> </SectionCard> <SectionCard title="Configuration" description="Visibility, schedule and budget for the project."> <div class="grid gap-4 sm:grid-cols-3"> <div class="space-y-2"> <Label for="fl-visibility">Visibility</Label> <Select bind:value={visibility}> <SelectTrigger id="fl-visibility" class="w-full"> <SelectValue /> </SelectTrigger> <SelectContent> <SelectItem value="public">Public</SelectItem> <SelectItem value="internal">Internal</SelectItem> <SelectItem value="private">Private</SelectItem> </SelectContent> </Select> </div> <div class="space-y-2"> <Label for="fl-start-date">Start date</Label> <Input id="fl-start-date" bind:value={startDate} type="date" /> </div> <div class="space-y-2"> <Label for="fl-budget">Budget</Label> <Input id="fl-budget" bind:value={budget} type="number" min={0} step={500} class="text-left" /> </div> </div> </SectionCard> <SectionCard title="Access" description="Decide who can open the project and what they can do."> <div class="space-y-5"> <RadioGroup bind:value={access} class="gap-3"> <div class="hover:bg-accent/50 has-data-[state=checked]:border-primary flex items-start gap-3 rounded-lg border p-3 transition-colors" > <RadioGroupItem id="fl-access-link" value="link" class="mt-0.5" /> <label for="fl-access-link" class="cursor-pointer select-none"> <span class="flex items-center gap-1.5 text-sm font-medium"> <Link2 class="size-4" aria-hidden="true" /> Anyone with the link </span> <span class="text-muted-foreground mt-0.5 block text-xs"> Guests can view without signing in. </span> </label> </div> <div class="hover:bg-accent/50 has-data-[state=checked]:border-primary flex items-start gap-3 rounded-lg border p-3 transition-colors" > <RadioGroupItem id="fl-access-restricted" value="restricted" class="mt-0.5" /> <label for="fl-access-restricted" class="cursor-pointer select-none"> <span class="flex items-center gap-1.5 text-sm font-medium"> <Lock class="size-4" aria-hidden="true" /> Restricted </span> <span class="text-muted-foreground mt-0.5 block text-xs"> Only invited members can open the project. </span> </label> </div> </RadioGroup> <div class="divide-y rounded-lg border"> <div class="flex items-center justify-between gap-4 p-4"> <div class="min-w-0"> <p class="text-sm font-medium">Notifications</p> <p class="text-muted-foreground mt-0.5 text-xs">Email the team on status changes and mentions.</p> </div> <Switch bind:checked={notifications} aria-label="Toggle notifications" /> </div> <div class="flex items-center justify-between gap-4 p-4"> <div class="min-w-0"> <p class="text-sm font-medium">Comments</p> <p class="text-muted-foreground mt-0.5 text-xs">Let viewers comment on tasks and updates.</p> </div> <Switch bind:checked={comments} aria-label="Toggle comments" /> </div> </div> </div> </SectionCard> <div class="bg-background/80 border-border sticky bottom-0 -mx-1 flex items-center justify-end gap-2 rounded-b-xl border-t px-1 py-4 backdrop-blur" > <Button type="button" variant="ghost" size="sm">Cancel</Button> <Button type="submit" size="sm" disabled={name.trim() === ''}>Create project</Button> </div> </form> </div>
Raw manifest:https://uipkge.dev/r/svelte/form-layouts.json