Menu

Change language

GitHub repo

Error Pages

blocklayout

Pack 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

$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/error-pages.json
Manifest:https://uipkge.dev/r/svelte/error-pages.jsonInstalls to:src/lib/components/blocks/error-pages/

Variants

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

Raw manifest:https://uipkge.dev/r/svelte/error-pages.json