Error Pages
blocklayoutPack of four status screens — 404, 500, 403, and 503 — each with a matching isometric illustration, code, copy, and actions. `variant` picks a single screen, `size` compacts it. Override title, description, image, code, tag, labels, hrefs, disabled flags, and callbacks. Pass `image=""` to hide the art.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/error-pages.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/error-pages.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/error-pages.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/error-pages.jsonManifest:
https://uipkge.dev/r/svelte/error-pages.jsonInstalls to:src/lib/components/blocks/error-pages/Variants
Loading interactive previews…
Loading interactive previews…
npm dependencies
Includes
Files installed (2)
src/lib/components/blocks/error-pages/ErrorPages.svelte12.2 kB
<script lang="ts" module> import type { Component } from 'svelte' export type ErrorPagesVariant = '404' | '500' | '403' | 'maintenance' export interface ErrorPagesProps { /** `'all'` renders the full gallery; a single variant renders just that screen. */ variant?: ErrorPagesVariant | 'all' size?: 'sm' | 'default' class?: string title?: string description?: string /** Status numeral. Pass `''` to hide. */ code?: string /** Eyebrow pill (maintenance ETA by default). Pass `''` to hide. */ tag?: string /** Replaces the HTTP response panel on a single variant. Pass `''` to hide art. */ image?: string imageAlt?: string primaryLabel?: string secondaryLabel?: string primaryHref?: string secondaryHref?: string showPrimary?: boolean showSecondary?: boolean primaryDisabled?: boolean secondaryDisabled?: boolean onHome?: () => void onSearch?: () => void onRetry?: () => void onStatus?: () => void onSwitchAccount?: () => void onRequestAccess?: () => void onSubscribe?: () => void } </script> <script lang="ts"> import { Clock, RotateCcw } from '@lucide/svelte' import { cn } from '$lib/utils' import { Button, buttonVariants, type ButtonVariants } from '$lib/components/ui/button' let { variant = 'all', size = 'default', class: className, title, description, code, tag, image, imageAlt = '', primaryLabel, secondaryLabel, primaryHref, secondaryHref, showPrimary = true, showSecondary = true, primaryDisabled = false, secondaryDisabled = false, onHome, onSearch, onRetry, onStatus, onSwitchAccount, onRequestAccess, onSubscribe, }: ErrorPagesProps = $props() const show = (v: ErrorPagesVariant) => variant === 'all' || variant === v const copy = (fallback: string, override?: string) => (override === undefined ? fallback : override) const forVariant = (v: ErrorPagesVariant, fallback: string, override?: string) => variant === v ? copy(fallback, override) : fallback const title404 = $derived(forVariant('404', 'Page not found', title)) const desc404 = $derived( forVariant( '404', "The page you're looking for doesn't exist or was moved. Check the URL or head back home.", description, ), ) const code404 = $derived(forVariant('404', '404', code)) const image404 = $derived(forVariant('404', '/illustrations/error-404.jpg', image)) const primary404 = $derived(forVariant('404', 'Go home', primaryLabel)) const secondary404 = $derived(forVariant('404', 'Search docs', secondaryLabel)) const title500 = $derived(forVariant('500', 'Internal server error', title)) const desc500 = $derived( forVariant( '500', 'Something went wrong on our end. The team has been notified — try again in a moment.', description, ), ) const code500 = $derived(forVariant('500', '500', code)) const image500 = $derived(forVariant('500', '/illustrations/error-500.jpg', image)) const primary500 = $derived(forVariant('500', 'Retry', primaryLabel)) const secondary500 = $derived(forVariant('500', 'Status page', secondaryLabel)) const title403 = $derived(forVariant('403', 'Access denied', title)) const desc403 = $derived( forVariant( '403', "You don't have permission to view this page. Switch accounts or ask an admin to request access.", description, ), ) const code403 = $derived(forVariant('403', '403', code)) const image403 = $derived(forVariant('403', '/illustrations/error-403.jpg', image)) const primary403 = $derived(forVariant('403', 'Request access', primaryLabel)) const secondary403 = $derived(forVariant('403', 'Switch account', secondaryLabel)) const title503 = $derived(forVariant('maintenance', 'Scheduled maintenance', title)) const desc503 = $derived( forVariant('maintenance', "We're upgrading our systems. The service will be back online shortly.", description), ) const code503 = $derived(forVariant('maintenance', '503', code)) const image503 = $derived(forVariant('maintenance', '/illustrations/error-maintenance.jpg', image)) const tag503 = $derived(forVariant('maintenance', 'Back online around 14:00 UTC', tag)) const primary503 = $derived(forVariant('maintenance', 'Subscribe for updates', primaryLabel)) const secondary503 = $derived(variant === 'maintenance' ? (secondaryLabel ?? '') : (secondaryLabel ?? '')) const frameClass = $derived( cn('border-border bg-card overflow-hidden rounded-xl border text-left', size === 'sm' && 'rounded-lg'), ) const imgClass = 'aspect-[16/10] w-full object-cover' const panelClass = 'border-border bg-muted/40 border-b font-mono' const bodyClass = $derived(size === 'sm' ? 'p-5' : 'p-6') const codeClass = $derived(cn('font-semibold tracking-tighter', size === 'sm' ? 'text-2xl' : 'text-4xl')) const titleClass = $derived(cn('mt-1.5 font-semibold tracking-tight', size === 'sm' ? 'text-base' : 'text-xl')) const descClass = 'text-muted-foreground mt-2 max-w-sm text-sm' const actionsClass = $derived(cn('flex flex-wrap items-center gap-2', size === 'sm' ? 'mt-4' : 'mt-5')) const actionSize = $derived(size === 'sm' ? 'xs' : 'sm') </script> {#snippet actionButton( href: string | undefined, label: string, variantName: ButtonVariants['variant'] = 'default', disabled = false, onclick?: () => void, Icon?: Component, )} {#if href} <a {href} class={buttonVariants({ variant: variantName, size: actionSize })} {onclick} aria-disabled={disabled}> {#if Icon} <Icon /> {/if} {label} </a> {:else} <Button variant={variantName} size={actionSize} {disabled} {onclick}> {#if Icon} <Icon /> {/if} {label} </Button> {/if} {/snippet} <div data-slot="error-pages" data-size={size} class={cn(variant === 'all' && 'grid gap-4 sm:grid-cols-2', className)}> {#if show('404')} <article class={frameClass} data-slot="error-page" data-variant="404" role="status"> {#if image404} <img src={image404} alt={imageAlt} class={imgClass} /> {:else if image404 === undefined} <div class={panelClass} aria-hidden="true"> <div class="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs">GET /pricing/teams</div> <div class="space-y-1 p-3 text-xs leading-relaxed"> <p><span class="text-muted-foreground">HTTP/1.1</span> {code404 || '404'} Not Found</p> <p class="text-muted-foreground">x-request-id: req_9k2e18</p> </div> </div> {/if} <div class={bodyClass}> {#if code404} <p class={codeClass}>{code404}</p> {/if} <h3 class={titleClass}>{title404}</h3> <p class={descClass}>{desc404}</p> {#if variant === '404' && tag} <p class="text-muted-foreground mt-3 inline-flex items-center rounded-full border px-3 py-1 text-xs"> {tag} </p> {/if} {#if showPrimary || showSecondary} <div class={actionsClass}> {#if showPrimary} {@render actionButton(primaryHref, primary404, 'default', primaryDisabled, onHome)} {/if} {#if showSecondary} {@render actionButton(secondaryHref, secondary404, 'ghost', secondaryDisabled, onSearch)} {/if} </div> {/if} </div> </article> {/if} {#if show('500')} <article class={frameClass} data-slot="error-page" data-variant="500" role="alert"> {#if image500} <img src={image500} alt={imageAlt} class={imgClass} /> {:else if image500 === undefined} <div class={panelClass} aria-hidden="true"> <div class="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs">POST /api/v1/invoices</div> <div class="space-y-1 p-3 text-xs leading-relaxed"> <p> <span class="text-muted-foreground">HTTP/1.1</span> <span class="text-destructive">{code500 || '500'} Internal Server Error</span> </p> <p class="text-muted-foreground">error: ECONNRESET at Worker.run:142</p> </div> </div> {/if} <div class={bodyClass}> {#if code500} <p class={codeClass}>{code500}</p> {/if} <h3 class={titleClass}>{title500}</h3> <p class={descClass}>{desc500}</p> {#if variant === '500' && tag} <p class="text-muted-foreground mt-3 inline-flex items-center rounded-full border px-3 py-1 text-xs"> {tag} </p> {/if} {#if showPrimary || showSecondary} <div class={actionsClass}> {#if showPrimary} {@render actionButton(primaryHref, primary500, 'default', primaryDisabled, onRetry, RotateCcw)} {/if} {#if showSecondary} {@render actionButton(secondaryHref, secondary500, 'outline', secondaryDisabled, onStatus)} {/if} </div> {/if} </div> </article> {/if} {#if show('403')} <article class={frameClass} data-slot="error-page" data-variant="403" role="alert"> {#if image403} <img src={image403} alt={imageAlt} class={imgClass} /> {:else if image403 === undefined} <div class={panelClass} aria-hidden="true"> <div class="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs">GET /admin/billing</div> <div class="space-y-1 p-3 text-xs leading-relaxed"> <p> <span class="text-muted-foreground">HTTP/1.1</span> <span class="text-warning">{code403 || '403'} Forbidden</span> </p> <p class="text-muted-foreground">scope billing:read — denied for guest</p> </div> </div> {/if} <div class={bodyClass}> {#if code403} <p class={codeClass}>{code403}</p> {/if} <h3 class={titleClass}>{title403}</h3> <p class={descClass}>{desc403}</p> {#if variant === '403' && tag} <p class="text-muted-foreground mt-3 inline-flex items-center rounded-full border px-3 py-1 text-xs"> {tag} </p> {/if} {#if showPrimary || showSecondary} <div class={actionsClass}> {#if showSecondary} {@render actionButton(secondaryHref, secondary403, 'outline', secondaryDisabled, onSwitchAccount)} {/if} {#if showPrimary} {@render actionButton(primaryHref, primary403, 'default', primaryDisabled, onRequestAccess)} {/if} </div> {/if} </div> </article> {/if} {#if show('maintenance')} <article class={frameClass} data-slot="error-page" data-variant="maintenance" role="status"> {#if image503} <img src={image503} alt={imageAlt} class={imgClass} /> {:else if image503 === undefined} <div class={panelClass} aria-hidden="true"> <div class="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs">GET /health</div> <div class="space-y-1 p-3 text-xs leading-relaxed"> <p> <span class="text-muted-foreground">HTTP/1.1</span> <span class="text-warning">{code503 || '503'} Service Unavailable</span> </p> <p class="text-muted-foreground">Retry-After: 3600</p> </div> </div> {/if} <div class={bodyClass}> {#if code503} <p class={codeClass}>{code503}</p> {/if} <h3 class={titleClass}>{title503}</h3> <p class={descClass}>{desc503}</p> {#if tag503} <p class="text-muted-foreground mt-3 inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs"> <Clock class="size-3.5" aria-hidden="true" /> {tag503} </p> {/if} {#if showPrimary || (showSecondary && secondary503)} <div class={actionsClass}> {#if showPrimary} {@render actionButton(primaryHref, primary503, 'ghost', primaryDisabled, onSubscribe)} {/if} {#if showSecondary && secondary503} {@render actionButton(secondaryHref, secondary503, 'outline', secondaryDisabled, onStatus)} {/if} </div> {/if} </div> </article> {/if} </div>src/routes/error-pages-demo/+page.svelte0.5 kB
<script lang="ts"> // Demo page wired by `npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/error-pages.json`. // Visit /error-pages-demo after install. import ErrorPages from '$lib/components/blocks/error-pages/ErrorPages.svelte' </script> <div class="bg-background min-h-svh"> <header class="border-border border-b px-8 py-4"> <h1 class="text-muted-foreground font-mono text-sm tracking-widest break-all uppercase">error-pages demo</h1> </header> <main class="p-8"> <ErrorPages /> </main> </div>
Raw manifest:https://uipkge.dev/r/svelte/error-pages.json