Utility components provide common functionality like loading states, error handling, empty states, and other helper UI elements. These components are located in src/lib/components/ and are used throughout the application.
Simple loading spinner with customizable size and message.
< script lang = "ts" >
import LoadingSpinner from '$lib/components/LoadingSpinner.svelte' ;
</ script >
<!-- Basic usage -->
< LoadingSpinner />
<!-- With custom message -->
< LoadingSpinner message = "Loading data..." />
<!-- Different sizes -->
< LoadingSpinner size = "sm" message = "Loading..." />
< LoadingSpinner size = "md" message = "Please wait..." />
< LoadingSpinner size = "lg" message = "Loading content..." />
Props:
message: string - Loading message (default: "Loading...")size: 'sm' | 'md' | 'lg' - Spinner size (default: "md")Size Classes:
sm: 8x8 spinner, small textmd: 12x12 spinner, base text (default)lg: 16x16 spinner, large textFull-screen loading overlay for page transitions.
< script lang = "ts" >
import LoadingScreen from '$lib/components/LoadingScreen.svelte' ;
let loading = $ state ( true );
</ script >
{# if loading}
< LoadingScreen message = "Loading application..." />
{/ if }
Props:
message: string - Loading messagefullScreen: boolean - Cover entire screen (default: true)Use Cases:
Initial app load Route transitions Authentication checks Critical data loading Error boundary component that catches and displays errors gracefully.
< script lang = "ts" >
import ErrorBoundary from '$lib/components/ErrorBoundary.svelte' ;
let error = $ state < Error | null >( null );
</ script >
< ErrorBoundary { error } >
{# snippet children ()}
<!-- Component that might error -->
< MyComponent />
{/ snippet }
{# snippet fallback (error)}
< div class = "p-4 border border-red-500 rounded-lg" >
< h3 class = "text-red-600 font-semibold" >Error</ h3 >
< p class = "text-sm text-red-500" >{error.message}</ p >
</ div >
{/ snippet }
</ ErrorBoundary >
Props:
error: Error | null - Error objectchildren: Snippet - Content to wrapfallback: (error: Error) => Snippet - Error displayFeatures:
Catches component errors Displays error message Provides retry option Logs errors for debugging Display when there's no data to show.
< script lang = "ts" >
import EmptyState from '$lib/components/EmptyState.svelte' ;
import { DocumentText, InboxIcon } from 'svelte-hero-icons' ;
let items = $ state < any []>([]);
</ script >
{# if items. length === 0 }
< EmptyState
title = "No items found"
message = "Get started by creating your first item"
icon ={InboxIcon}
size = "md"
/>
{/ if }
Props:
title: string - Empty state title (required)message: string - Empty state message (required)icon: any - Heroicon component (default: DocumentText)size: 'sm' | 'md' | 'lg' - Size variant (default: "md")Size Variants:
sm: Small icon (6x6), base textmd: Medium icon (8x8), large text (default)lg: Large icon (10x10), xl textCommon Use Cases:
Empty lists No search results No data available Initial state
< script lang = "ts" >
import EmptyState from '$lib/components/EmptyState.svelte' ;
import { Button } from '$lib/components/ui' ;
import { PlusIcon } from 'svelte-hero-icons' ;
</ script >
< EmptyState
title = "No assessments yet"
message = "Create your first assessment to get started"
icon ={PlusIcon}
>
{# snippet action ()}
< Button variant = "primary" icon ={PlusIcon} onclick ={() => createAssessment ()}>
Create Assessment
</ Button >
{/ snippet }
</ EmptyState >
Display file information with preview and actions.
< script lang = "ts" >
import FileCard from '$lib/components/FileCard.svelte' ;
const file = {
id: 1 ,
name: 'document.pdf' ,
size: 1024000 ,
type: 'application/pdf' ,
uploadedAt: new Date (),
url: '/files/document.pdf'
};
</ script >
< FileCard { file } />
Props:
file: FileInfo - File information objectonDownload: () => void - Download callbackonDelete: () => void - Delete callbackshowActions: boolean - Show action buttonsFileInfo Interface:
interface FileInfo {
id : number | string ;
name : string ;
size : number ; // bytes
type : string ; // MIME type
uploadedAt : Date ;
url : string ;
thumbnailUrl ?: string ;
}
Features:
File type icons Size formatting Preview thumbnails Download/delete actions Upload date display Button component for file uploads with drag-and-drop support.
< script lang = "ts" >
import FileUploadButton from '$lib/components/FileUploadButton.svelte' ;
function handleUpload ( files : File []) {
console. log ( 'Uploaded files:' , files);
// Upload logic
}
function handleError ( error : Error ) {
console. error ( 'Upload error:' , error);
}
</ script >
< FileUploadButton
accept = ".pdf,.doc,.docx"
multiple ={ true }
maxSize ={ 10 * 1024 * 1024 } // 10 MB
onUpload ={handleUpload}
onError ={handleError}
/>
Props:
accept: string - Accepted file types (e.g., ".pdf,.doc")multiple: boolean - Allow multiple filesmaxSize: number - Maximum file size in bytesonUpload: (files: File[]) => void - Upload callbackonError: (error: Error) => void - Error callbackdisabled: boolean - Disable uploadFeatures:
Drag and drop File type validation Size validation Progress indication Error handling Individual toast notification component.
< script lang = "ts" >
import Toast from '$lib/components/Toast.svelte' ;
const toast = {
id: '1' ,
message: 'Operation completed successfully' ,
type: 'success' ,
duration: 5000
};
</ script >
< Toast { toast } />
Props:
toast: ToastData - Toast data objectonDismiss: (id: string) => void - Dismiss callbackToastData Interface:
interface ToastData {
id : string ;
message : string ;
type : 'success' | 'error' | 'info' | 'warning' ;
duration ?: number ; // milliseconds
}
Container for managing multiple toast notifications.
< script lang = "ts" >
import ToastContainer from '$lib/components/ToastContainer.svelte' ;
import { toast } from '$lib/stores/toast' ;
</ script >
< ToastContainer />
<!-- Usage -->
< script >
toast. success ( 'Saved successfully!' );
toast. error ( 'Failed to save' );
toast. info ( 'New update available' );
toast. warning ( 'Please review your changes' );
</ script >
Toast Store 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)toast.clear()Features:
Auto-dismiss after duration Manual dismiss Stack multiple toasts Position customization Animation support Table component for displaying raw data with minimal styling.
< script lang = "ts" >
import RawDataTable from '$lib/components/RawDataTable.svelte' ;
let data = $ state ([
{ key: 'Name' , value: 'John Doe' },
{ key: 'Email' , value: 'john@example.com' },
{ key: 'Role' , value: 'Admin' }
]);
</ script >
< RawDataTable { data } />
Props:
data: Record<string, any>[] - Data to displayheaders: string[] - Column headers (optional)striped: boolean - Alternating row colorsFilter component for selecting academic years.
< script lang = "ts" >
import YearFilter from '$lib/components/YearFilter.svelte' ;
let selectedYear = $ state ( 2024 );
let availableYears = $ state ([ 2022 , 2023 , 2024 , 2025 ]);
</ script >
< YearFilter
bind : selectedYear
{ availableYears }
onYearChange ={( year ) => console. log ( 'Year changed:' , year)}
/>
Props:
selectedYear: number - Currently selected year (use bind:selectedYear)availableYears: number[] - Available yearsonYearChange: (year: number) => void - Year change callbackTranslation component for i18n.
< script lang = "ts" >
import T from '$lib/components/T.svelte' ;
</ script >
<!-- Basic usage -->
< T key = "welcome.message" />
<!-- With variables -->
< T key = "user.greeting" name = "John" />
<!-- With default -->
< T key = "optional.message" default = "Default text" />
Props:
key: string - Translation keydefault: string - Default text if key not foundAdditional props passed as translation variables Component for building and customizing themes.
< script lang = "ts" >
import ThemeBuilder from '$lib/components/ThemeBuilder.svelte' ;
let theme = $ state ({
name: 'Custom Theme' ,
colors: {
primary: '#6366f1' ,
secondary: '#8b5cf6'
}
});
</ script >
< ThemeBuilder bind : theme } />
Props:
theme: Theme - Theme object (use bind:theme)onSave: (theme: Theme) => void - Save callbackFeatures:
Color picker Preview Export/import Reset to default
< script lang = "ts" >
import LoadingSpinner from '$lib/components/LoadingSpinner.svelte' ;
import EmptyState from '$lib/components/EmptyState.svelte' ;
let data = $ state < any []>([]);
let loading = $ state ( true );
let error = $ state < Error | null >( null );
</ script >
{# if loading}
< LoadingSpinner message = "Loading data..." />
{: else if error}
< div class = "p-4 border border-red-500 rounded-lg" >
< p class = "text-red-600" >{error.message}</ p >
< Button onclick ={() => retry ()}>Retry</ Button >
</ div >
{: else if data. length === 0 }
< EmptyState
title = "No data"
message = "There's nothing here yet"
/>
{: else }
<!-- Display data -->
{# each data as item}
< div >{item.name}</ div >
{/ each }
{/ if }
< script lang = "ts" >
import ErrorBoundary from '$lib/components/ErrorBoundary.svelte' ;
import { toast } from '$lib/stores/toast' ;
let error = $ state < Error | null >( null );
async function loadData () {
try {
error = null ;
// Load data
} catch (e) {
error = e instanceof Error ? e : new Error ( 'Unknown error' );
toast. error ( 'Failed to load data' );
}
}
</ script >
< ErrorBoundary { error } >
{# snippet children ()}
<!-- Content -->
{/ snippet }
{# snippet fallback (error)}
< div class = "p-4" >
< h3 >Error</ h3 >
< p >{error.message}</ p >
< Button onclick ={loadData}>Retry</ Button >
</ div >
{/ snippet }
</ ErrorBoundary >
< script lang = "ts" >
import FileUploadButton from '$lib/components/FileUploadButton.svelte' ;
import FileCard from '$lib/components/FileCard.svelte' ;
import { toast } from '$lib/stores/toast' ;
let files = $ state < FileInfo []>([]);
async function handleUpload ( uploadedFiles : File []) {
try {
// Upload files
const uploaded = await uploadFiles (uploadedFiles);
files = [ ... files, ... uploaded];
toast. success ( `Uploaded ${ uploadedFiles . length } file(s)` );
} catch (error) {
toast. error ( 'Failed to upload files' );
}
}
</ script >
< div class = "space-y-4" >
< FileUploadButton
accept = ".pdf,.doc,.docx"
multiple ={ true }
onUpload ={handleUpload}
/>
< div class = "grid grid-cols-2 gap-4" >
{# each files as file}
< FileCard { file } />
{/ each }
</ div >
</ div >
Loading States : Always show loading indicators for async operationsError Handling : Provide clear error messages and retry optionsEmpty States : Use EmptyState to guide users when there's no dataToast Notifications : Use toasts for non-critical feedbackFile Handling : Validate file types and sizes before uploadAccessibility : Include proper ARIA labels for screen readersPerformance : Lazy load heavy utility components
< script lang = "ts" >
import LoadingSpinner from '$lib/components/LoadingSpinner.svelte' ;
import EmptyState from '$lib/components/EmptyState.svelte' ;
import ErrorBoundary from '$lib/components/ErrorBoundary.svelte' ;
import { toast } from '$lib/stores/toast' ;
let data = $ state < any []>([]);
let loading = $ state ( true );
let error = $ state < Error | null >( null );
async function loadData () {
loading = true ;
error = null ;
try {
data = await fetchData ();
} catch (e) {
error = e instanceof Error ? e : new Error ( 'Failed to load' );
toast. error ( 'Failed to load data' );
} finally {
loading = false ;
}
}
onMount (loadData);
</ script >
< ErrorBoundary { error } >
{# snippet children ()}
{# if loading}
< LoadingSpinner message = "Loading..." />
{: else if data. length === 0 }
< EmptyState
title = "No data available"
message = "There's nothing to display"
/>
{: else }
<!-- Display data -->
{/ if }
{/ snippet }
</ ErrorBoundary >