Search Results Page
blocklayoutFull search results page: large pre-filled search input with result count, removable filter chips with clear-all, a left facet sidebar with Type and Team checkbox groups (hidden below lg), six result rows with icon tiles, titles, paths, snippets, and type/updated meta, plus a Previous/Next pagination footer. Chips and facets are interactive; swap the stub rows for your search source.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/search-results-page.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/search-results-page.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/search-results-page.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/search-results-page.jsonManifest:
https://uipkge.dev/r/svelte/search-results-page.jsonInstalls to:src/lib/components/blocks/search-results-page/Variants
Loading interactive previews…
Loading interactive previews…
Files installed (1)
src/lib/components/blocks/search-results-page/SearchResultsPage.svelte8.7 kB
<script lang="ts" module> import type { Component } from 'svelte' export interface SearchResult { id: string icon: Component title: string path: string snippet: string type: string updated: string } export interface FacetOption { id: string label: string count: number } export interface FacetGroup { id: string label: string options: FacetOption[] } export interface SearchResultsPageProps { class?: string } </script> <script lang="ts"> import { BookOpen, FileText, GitBranch, MessageSquare, Search, Users, Video } from '@lucide/svelte' import { cn } from '$lib/utils' import { Badge } from '$lib/components/ui/badge' import { Button } from '$lib/components/ui/button' import { Checkbox } from '$lib/components/ui/checkbox' import { Chip } from '$lib/components/ui/chip' import { Input } from '$lib/components/ui/input' let { class: className }: SearchResultsPageProps = $props() let query = $state('onboarding flows') let chips = $state([ { id: 'type-docs', label: 'Type: Docs' }, { id: 'team-product', label: 'Team: Product' }, ]) function removeChip(id: string) { chips = chips.filter((chip) => chip.id !== id) } function clearAll() { chips = [] } const facetGroups: FacetGroup[] = [ { id: 'type', label: 'Type', options: [ { id: 'type-docs', label: 'Docs', count: 18 }, { id: 'type-guides', label: 'Guides', count: 11 }, { id: 'type-video', label: 'Video', count: 7 }, { id: 'type-discussion', label: 'Discussion', count: 6 }, ], }, { id: 'team', label: 'Team', options: [ { id: 'team-product', label: 'Product', count: 14 }, { id: 'team-engineering', label: 'Engineering', count: 16 }, { id: 'team-design', label: 'Design', count: 8 }, { id: 'team-support', label: 'Support', count: 4 }, ], }, ] let activeFacets = $state(new Set(['type-docs', 'team-product'])) function toggleFacet(id: string, checked: boolean) { const next = new Set(activeFacets) if (checked) next.add(id) else next.delete(id) activeFacets = next } const results: SearchResult[] = [ { id: 'r1', icon: BookOpen, title: 'Onboarding flows: design your first-run experience', path: 'docs.acme.dev / guides / onboarding-flows', snippet: 'Learn how to compose onboarding flows from checklists, tours, and progressive disclosure so new users reach their first aha moment faster.', type: 'Docs', updated: '2 days ago', }, { id: 'r2', icon: FileText, title: 'Checklist API reference for onboarding flows', path: 'docs.acme.dev / api / checklist', snippet: 'Full reference for the Checklist primitive used to drive onboarding flows, including step completion events, persistence, and theming.', type: 'Reference', updated: '5 hours ago', }, { id: 'r3', icon: Video, title: 'Video walkthrough: building onboarding flows in 20 minutes', path: 'learn.acme.dev / videos / onboarding-flows-walkthrough', snippet: 'A hands-on screencast that builds a complete onboarding flow with branching steps, skippable tours, and analytics instrumentation.', type: 'Video', updated: '1 week ago', }, { id: 'r4', icon: MessageSquare, title: 'How do you measure completion of onboarding flows?', path: 'community.acme.dev / discussions / 4821', snippet: 'Community thread comparing activation-rate funnels, step-drop-off charts, and qualitative surveys for measuring onboarding flow success.', type: 'Discussion', updated: '3 days ago', }, { id: 'r5', icon: GitBranch, title: 'Example repo: onboarding flows with A/B-tested variants', path: 'github.com / acme / examples / onboarding-ab-test', snippet: 'Runnable example that splits new signups into two onboarding flow variants and reports which one converts better to first project creation.', type: 'Example', updated: '2 weeks ago', }, { id: 'r6', icon: Users, title: 'Playbook: personalizing onboarding flows by team role', path: 'docs.acme.dev / playbooks / role-based-onboarding', snippet: 'Patterns for tailoring onboarding flows to admins, editors, and viewers, with sample copy, suggested defaults, and rollout checklists.', type: 'Guide', updated: '4 days ago', }, ] let page = $state(1) const totalPages = 3 const resultRange = $derived.by(() => { const start = (page - 1) * 6 + 1 return `${start}-${start + results.length - 1}` }) </script> <div data-slot="search-results-page" class={cn('w-full space-y-6', className)}> <div class="space-y-3"> <div class="relative max-w-xl"> <Search aria-hidden="true" class="text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2" /> <Input bind:value={query} type="search" placeholder="Search..." class="h-11 pl-9 text-base" /> </div> <p class="text-muted-foreground text-sm">{resultRange} of 42 results for “{query}”</p> </div> {#if chips.length > 0} <div class="flex flex-wrap items-center gap-2"> {#each chips as chip (chip.id)} <Chip variant="outline" size="sm" closable onclose={() => removeChip(chip.id)}> {chip.label} </Chip> {/each} <Button variant="link" size="sm" class="text-muted-foreground h-auto min-h-6 px-2" onclick={clearAll}> Clear all </Button> </div> {/if} <div class="flex gap-8"> <aside class="hidden w-48 shrink-0 space-y-6 lg:block" aria-label="Filters"> {#each facetGroups as group (group.id)} <div> <h3 class="text-muted-foreground mb-3 text-xs font-medium tracking-wider uppercase"> {group.label} </h3> <ul class="space-y-2.5"> {#each group.options as option (option.id)} <li> <label for={`${group.id}-${option.id}`} class="flex cursor-pointer items-center gap-2 text-sm"> <Checkbox id={`${group.id}-${option.id}`} checked={activeFacets.has(option.id)} onCheckedChange={(v) => toggleFacet(option.id, v === true)} /> <span>{option.label}</span> <span class="text-muted-foreground ml-auto text-xs tabular-nums">{option.count}</span> </label> </li> {/each} </ul> </div> {/each} </aside> <div class="min-w-0 flex-1"> <ol class="divide-border divide-y"> {#each results as result (result.id)} {@const ResultIcon = result.icon} <li> <a href="#" class="hover:bg-muted/50 focus-visible:ring-ring block rounded-lg p-4 transition-colors focus-visible:ring-2 focus-visible:outline-none" > <div class="flex gap-4"> <div class="bg-muted/50 text-muted-foreground flex size-10 shrink-0 items-center justify-center rounded-md border" > <ResultIcon aria-hidden="true" class="size-5" /> </div> <div class="min-w-0 space-y-1"> <p class="truncate font-medium">{result.title}</p> <p class="text-muted-foreground truncate text-xs">{result.path}</p> <p class="text-muted-foreground line-clamp-2 text-sm">{result.snippet}</p> <div class="flex items-center gap-3 pt-1"> <Badge variant="secondary">{result.type}</Badge> <span class="text-muted-foreground text-xs">Updated {result.updated}</span> </div> </div> </div> </a> </li> {/each} </ol> <nav class="flex items-center justify-between pt-4" aria-label="Pagination"> <Button variant="outline" size="sm" disabled={page === 1} onclick={() => page--}>Previous</Button> <div class="flex items-center gap-1"> {#each Array.from({ length: totalPages }, (_, i) => i + 1) as n (n)} <Button variant={n === page ? 'outline' : 'ghost'} size="icon-sm" aria-current={n === page ? 'page' : undefined} onclick={() => (page = n)} > {n} </Button> {/each} </div> <Button variant="outline" size="sm" disabled={page === totalPages} onclick={() => page++}>Next</Button> </nav> </div> </div> </div>
Raw manifest:https://uipkge.dev/r/svelte/search-results-page.json