DesQTA includes a comprehensive set of reusable UI components located in src/lib/components/ui/. These components follow consistent design patterns and are used throughout the application.
A versatile button component with multiple variants, sizes, and states.
< script lang = "ts" >
import { Button } from '$lib/components/ui' ;
import { TrashIcon, SaveIcon } from 'svelte-hero-icons' ;
</ script >
<!-- Basic usage -->
< Button variant = "primary" size = "md" onclick ={() => console. log ( 'clicked' )}>
Click me
</ Button >
<!-- With icon -->
< Button variant = "danger" size = "lg" icon ={TrashIcon} iconPosition = "left" >
Delete
</ Button >
<!-- Loading state -->
< Button loading ={ true } disabled ={ false }>
Loading...
</ Button >
<!-- Full width -->
< Button fullWidth ={ true } variant = "primary" >
Submit
</ Button >
Props:
variant: 'primary' | 'secondary' | 'ghost' | 'danger' | 'success' | 'warning' | 'info' - Button style variantsize: 'xs' | 'sm' | 'md' | 'lg' | 'xl' - Button sizedisabled: boolean - Disable button interactionloading: boolean - Show loading spinnericon: any - Heroicon component to displayiconPosition: 'left' | 'right' - Icon placementfullWidth: boolean - Make button full widthtype: 'button' | 'submit' | 'reset' - Button typeonclick: (e: MouseEvent) => void - Click handlerariaLabel: string - Accessibility labelVariants:
primary: Accent color backgroundsecondary: Neutral gray backgroundghost: Transparent backgrounddanger: Red background for destructive actionssuccess: Green backgroundwarning: Yellow backgroundinfo: Blue backgroundA comprehensive input component with validation, icons, and error states.
< script lang = "ts" >
import { Input } from '$lib/components/ui' ;
import { EnvelopeIcon, LockIcon } from 'svelte-hero-icons' ;
let email = $ state ( '' );
let password = $ state ( '' );
let emailError = $ state < string | null >( null );
</ script >
<!-- Basic input -->
< Input
bind : value ={email}
type = "email"
label = "Email Address"
placeholder = "Enter your email"
leftIcon ={EnvelopeIcon}
error ={emailError}
required ={ true }
/>
<!-- Password input -->
< Input
bind : value ={password}
type = "password"
label = "Password"
placeholder = "Enter your password"
leftIcon ={LockIcon}
required ={ true }
/>
Props:
value: string - Input value (use bind:value)type: string - Input type (text, email, password, etc.)label: string - Label textplaceholder: string - Placeholder textleftIcon: any - Icon on the leftrightIcon: any - Icon on the righterror: string | null - Error messagerequired: boolean - Required field indicatordisabled: boolean - Disable inputreadonly: boolean - Read-only modeA specialized input for search functionality with debouncing.
< script lang = "ts" >
import { SearchInput } from '$lib/components/ui' ;
let searchQuery = $ state ( '' );
function handleSearch ( query : string ) {
console. log ( 'Searching for:' , query);
}
function clearSearch () {
searchQuery = '' ;
}
</ script >
< SearchInput
bind : value ={searchQuery}
placeholder = "Search items..."
debounceMs ={ 300 }
clearable ={ true }
onSearch ={handleSearch}
onClear ={clearSearch}
/>
Props:
value: string - Search query (use bind:value)placeholder: string - Placeholder textdebounceMs: number - Debounce delay in millisecondsclearable: boolean - Show clear buttononSearch: (query: string) => void - Search callbackonClear: () => void - Clear callbackA flexible container component with header/footer slots.
< script lang = "ts" >
import { Card, Button } from '$lib/components/ui' ;
</ script >
< Card variant = "elevated" padding = "lg" interactive ={ true }>
{# snippet header ()}
< h3 class = "text-lg font-semibold" >Card Title</ h3 >
{/ snippet }
< p class = "text-zinc-600 dark:text-zinc-400" >
Card content goes here
</ p >
{# snippet footer ()}
< div class = "flex gap-2" >
< Button variant = "primary" >Action</ Button >
< Button variant = "ghost" >Cancel</ Button >
</ div >
{/ snippet }
</ Card >
Props:
variant: 'default' | 'elevated' | 'outlined' | 'ghost' - Card stylepadding: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' - Content paddinginteractive: boolean - Make card clickableonclick: () => void - Click handler (when interactive)header: Snippet - Header slotfooter: Snippet - Footer slotchildren: Snippet - Main content slotVariants:
default: Standard card with borderelevated: Card with shadowoutlined: Transparent background with borderghost: Subtle backgroundDisplay status, categories, or counts with various styles.
< script lang = "ts" >
import { Badge } from '$lib/components/ui' ;
import { CheckIcon } from 'svelte-hero-icons' ;
</ script >
<!-- Basic badge -->
< Badge variant = "success" size = "md" >
Active
</ Badge >
<!-- Badge with dot -->
< Badge variant = "primary" dot ={ true }>
New
</ Badge >
<!-- Badge with icon -->
< Badge variant = "info" icon ={CheckIcon}>
Verified
</ Badge >
<!-- Removable badge -->
< Badge variant = "default" removable ={ true } onremove ={() => console. log ( 'removed' )}>
Removable Tag
</ Badge >
Props:
variant: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' - Badge colorsize: 'xs' | 'sm' | 'md' | 'lg' - Badge sizeicon: any - Icon to displaydot: boolean - Show colored dot instead of iconremovable: boolean - Show remove buttononremove: () => void - Remove callbackShow progress with different variants and animations.
< script lang = "ts" >
import { Progress } from '$lib/components/ui' ;
let progress = $ state ( 75 );
</ script >
< Progress
value ={progress}
max ={ 100 }
variant = "success"
size = "md"
showLabel ={ true }
label = "Processing..."
animated ={ true }
striped ={ true }
/>
Props:
value: number - Current progress valuemax: number - Maximum value (default: 100)variant: 'default' | 'primary' | 'success' | 'warning' | 'danger' - Progress colorsize: 'sm' | 'md' | 'lg' - Progress bar heightshowLabel: boolean - Show percentage labellabel: string - Custom label textanimated: boolean - Animate progress barstriped: boolean - Show striped patternA feature-rich table component with sorting, selection, and custom cells.
< script lang = "ts" >
import { DataTable } from '$lib/components/ui' ;
import { Button } from '$lib/components/ui' ;
let users = $ state ([
{ id: 1 , name: 'John Doe' , email: 'john@example.com' , role: 'Admin' },
{ id: 2 , name: 'Jane Smith' , email: 'jane@example.com' , role: 'User' }
]);
let isLoading = $ state ( false );
let selectedRows = $ state < number []>([]);
const columns = [
{ key: 'name' , title: 'Name' , sortable: true },
{ key: 'email' , title: 'Email' , sortable: true },
{ key: 'role' , title: 'Role' },
{
key: 'actions' ,
title: 'Actions' ,
cell : ( value : any , row : any ) => {
return `<button class="btn btn-sm">Edit</button>` ;
}
}
];
function handleSort ( column : string ) {
console. log ( 'Sort by:' , column);
}
function handleSelect ( row : any , selected : boolean ) {
if (selected) {
selectedRows = [ ... selectedRows, row.id];
} else {
selectedRows = selectedRows. filter ( id => id !== row.id);
}
}
</ script >
< DataTable
columns ={columns}
data ={users}
loading ={isLoading}
selectable ={ true }
onSort ={handleSort}
onRowSelect ={handleSelect}
/>
Props:
columns: Column[] - Column definitionsdata: any[] - Table dataloading: boolean - Show loading stateerror: string | null - Error messageselectable: boolean - Enable row selectiononSort: (column: string) => void - Sort callbackonRowSelect: (row: any, selected: boolean) => void - Selection callbackColumn Definition:
interface Column {
key : string ;
title : string ;
sortable ?: boolean ;
cell ?: ( value : any , row : any ) => string | HTMLElement ;
width ?: string ;
align ?: 'left' | 'center' | 'right' ;
}
Handle loading, error, and empty states for data components.
< script lang = "ts" >
import { AsyncWrapper } from '$lib/components/ui' ;
let items = $ state < any []>([]);
let isLoading = $ state ( true );
let error = $ state < string | null >( null );
async function refetch () {
isLoading = true ;
error = null ;
try {
items = await fetchItems ();
} catch (e) {
error = e instanceof Error ? e.message : 'Failed to load' ;
} finally {
isLoading = false ;
}
}
</ script >
< AsyncWrapper
loading ={isLoading}
error ={error}
data ={items}
empty ={items. length === 0 }
emptyTitle = "No items found"
emptyDescription = "Try adjusting your filters or create a new item"
onretry ={refetch}
>
{# snippet children (data)}
{# each data as item}
< div class = "p-4 border rounded-lg" >{item.name}</ div >
{/ each }
{/ snippet }
</ AsyncWrapper >
Props:
loading: boolean - Show loading stateerror: string | null - Error messagedata: any - Data to displayempty: boolean - Show empty stateemptyTitle: string - Empty state titleemptyDescription: string - Empty state descriptiononretry: () => void - Retry callbackchildren: (data: any) => Snippet - Content render functionCreate tabbed interfaces with different variants.
< script lang = "ts" >
import { Tabs } from '$lib/components/ui' ;
import { HomeIcon, CogIcon, HelpIcon } from 'svelte-hero-icons' ;
let activeTab = $ state ( 'overview' );
const tabs = [
{ id: 'overview' , label: 'Overview' , icon: HomeIcon },
{ id: 'settings' , label: 'Settings' , icon: CogIcon },
{ id: 'help' , label: 'Help' , icon: HelpIcon, disabled: true }
];
</ script >
< Tabs
tabs ={tabs}
bind : activeTab
variant = "pills"
fullWidth ={ true }
>
{# snippet children (activeTab)}
{# if activeTab === 'overview' }
< div >Overview content</ div >
{: else if activeTab === 'settings' }
< div >Settings content</ div >
{/ if }
{/ snippet }
</ Tabs >
Props:
tabs: Tab[] - Tab definitionsactiveTab: string - Active tab ID (use bind:activeTab)variant: 'default' | 'pills' | 'underline' - Tab stylefullWidth: boolean - Make tabs fill widthchildren: (activeTab: string) => Snippet - Content render functionTab Definition:
interface Tab {
id : string ;
label : string ;
icon ?: any ;
disabled ?: boolean ;
badge ?: string | number ;
}
Create dropdown menus with customizable items.
< script lang = "ts" >
import { Dropdown } from '$lib/components/ui' ;
import { PencilIcon, TrashIcon, DuplicateIcon } from 'svelte-hero-icons' ;
function edit () {
console. log ( 'Edit' );
}
function deleteItem () {
console. log ( 'Delete' );
}
function duplicate () {
console. log ( 'Duplicate' );
}
const items = [
{ id: 'edit' , label: 'Edit' , icon: PencilIcon, onClick: edit },
{ id: 'duplicate' , label: 'Duplicate' , icon: DuplicateIcon, onClick: duplicate },
{ id: 'sep1' , separator: true },
{ id: 'delete' , label: 'Delete' , icon: TrashIcon, danger: true , onClick: deleteItem }
];
</ script >
< Dropdown
items ={items}
buttonText = "Actions"
showChevron ={ true }
/>
Props:
items: DropdownItem[] - Menu itemsbuttonText: string - Button labelshowChevron: boolean - Show dropdown chevronvariant: 'default' | 'ghost' - Button variantplacement: 'bottom' | 'top' | 'left' | 'right' - Dropdown positionDropdownItem Definition:
interface DropdownItem {
id : string ;
label : string ;
icon ?: any ;
danger ?: boolean ;
disabled ?: boolean ;
separator ?: boolean ;
onClick ?: () => void ;
}
Display helpful information on hover/focus.
< script lang = "ts" >
import { Tooltip } from '$lib/components/ui' ;
import { Button } from '$lib/components/ui' ;
</ script >
< Tooltip content = "This is a helpful tooltip" placement = "top" >
< Button >Hover me</ Button >
</ Tooltip >
Props:
content: string - Tooltip textplacement: 'top' | 'bottom' | 'left' | 'right' - Tooltip positiondelay: number - Show delay in millisecondschildren: Snippet - Element to attach tooltip toDisplay temporary notifications (used with ToastContainer).
< script lang = "ts" >
import { toast } from '$lib/stores/toast' ;
function showSuccess () {
toast. success ( 'Operation completed successfully!' );
}
function showError () {
toast. error ( 'Something went wrong' );
}
function showInfo () {
toast. info ( 'Here is some information' );
}
</ script >
< Button onclick ={showSuccess}>Show Success</ Button >
< Button onclick ={showError}>Show Error</ Button >
< Button onclick ={showInfo}>Show Info</ Button >
Toast Methods:
toast.success(message: string, duration?: number)toast.error(message: string, duration?: number)toast.info(message: string, duration?: number)toast.warning(message: string, duration?: number)toast.dismiss(id: string)
< script lang = "ts" >
import { Card, Button, Badge, Input, SearchInput } from '$lib/components/ui' ;
let searchQuery = $ state ( '' );
let items = $ state ([
{ id: 1 , name: 'Item 1' , status: 'active' },
{ id: 2 , name: 'Item 2' , status: 'pending' }
]);
</ script >
< Card variant = "elevated" padding = "lg" >
{# snippet header ()}
< div class = "flex items-center justify-between" >
< h2 class = "text-xl font-semibold" >Items</ h2 >
< Button variant = "primary" size = "sm" >Add New</ Button >
</ div >
{/ snippet }
< div class = "space-y-4" >
< SearchInput bind : value ={searchQuery} placeholder = "Search items..." />
< div class = "space-y-2" >
{# each items as item}
< div class = "flex items-center justify-between p-3 border rounded-lg" >
< div class = "flex items-center gap-2" >
< span >{item.name}</ span >
< Badge variant ={item.status === 'active' ? 'success' : 'warning' }>
{item.status}
</ Badge >
</ div >
< Button variant = "ghost" size = "sm" >Edit</ Button >
</ div >
{/ each }
</ div >
</ div >
</ Card >
Components use CSS custom properties for theming:
--accent-* for primary colors--zinc-* for neutral colorsSemantic colors: red, green, yellow, blue for states Consistent size scale across components:
xs: Extra smallsm: Smallmd: Medium (default)lg: Largexl: Extra largeAll interactive elements use:
transition-all duration-200 for smooth transitionshover:scale-105 for hover effectsactive:scale-95 for click feedbackFocus rings for accessibility: focus:ring-2 focus:ring-offset-2 Composition over Configuration : Use slots and snippets for flexibilityConsistent Props : Similar props across components (size, variant, disabled, etc.)Error Handling : Always provide error states and fallbacksLoading States : Use AsyncWrapper for data-dependent componentsAccessibility : All components include proper ARIA attributesType Safety : Use TypeScript interfaces for all propsDark Mode : All components support dark mode automatically
// Individual imports
import { Button } from '$lib/components/ui/Button.svelte' ;
import { Card } from '$lib/components/ui/Card.svelte' ;
// Barrel import (recommended)
import { Button, Card, Input, Badge } from '$lib/components/ui' ;