Error Page
blocklayoutStatus-screen set with prop-driven 404 and 500 pages plus a four-variant gallery (404, 500, 403, and maintenance) with single-screen, compact-size, and callback controls.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/react/error-page.json$npx shadcn@latest add https://uipkge.dev/r/react/error-page.json$yarn dlx shadcn@latest add https://uipkge.dev/r/react/error-page.json$bunx shadcn@latest add https://uipkge.dev/r/react/error-page.jsonNamed registry:
npx shadcn@latest add @uipkge-react/error-pageInstalls to:components/blocks/error-page/Variants
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
code | string | — | optional |
title | string | — | optional |
description | string | — | optional |
imageReplaces the HTTP response panel. Pass `''` to hide art entirely. | string | — | optional |
imageAlt | string | — | optional |
primaryLabel | string | — | optional |
secondaryLabel | string | — | optional |
primaryHref | string | — | optional |
secondaryHref | string | — | optional |
showPrimary | boolean | — | optional |
showSecondary | boolean | — | optional |
primaryDisabled | boolean | — | optional |
secondaryDisabled | boolean | — | optional |
onPrimary | () => void | — | optional |
onSecondary | () => void | — | optional |
layout`'page'` fills the viewport; `'contained'` fits a preview or nested panel. | 'page' | 'contained' | — | optional |
className | string | — | optional |
npm dependencies
Includes
Files installed (3)
components/blocks/error-page/Error404.tsx4.8 kB
'use client' import { ArrowLeft, LifeBuoy } from 'lucide-react' import { cn } from '@/lib/utils' import { Button } from '@/components/ui/button' export interface Error404Props { code?: string title?: string description?: string /** Replaces the HTTP response panel. Pass `''` to hide art entirely. */ image?: string imageAlt?: string primaryLabel?: string secondaryLabel?: string primaryHref?: string secondaryHref?: string showPrimary?: boolean showSecondary?: boolean primaryDisabled?: boolean secondaryDisabled?: boolean onPrimary?: () => void onSecondary?: () => void /** `'page'` fills the viewport; `'contained'` fits a preview or nested panel. */ layout?: 'page' | 'contained' className?: string } const copy = (fallback: string, override?: string) => (override === undefined ? fallback : override) export function Error404({ code, title, description, image = '/illustrations/error-404.jpg', imageAlt = '', primaryLabel, secondaryLabel, primaryHref, secondaryHref, showPrimary = true, showSecondary = true, primaryDisabled = false, secondaryDisabled = false, onPrimary, onSecondary, layout = 'page', className, }: Error404Props) { const codeText = copy('404', code) const titleText = copy('Page not found', title) const descriptionText = copy( "The page you're looking for doesn't exist or may have been moved. Check the URL or head back home.", description, ) const primaryText = copy('Back to home', primaryLabel) const secondaryText = copy('Contact support', secondaryLabel) return ( <section data-slot="error-404" data-layout={layout} className={cn( 'bg-background relative flex items-center overflow-hidden px-6 py-16', layout === 'page' ? 'min-h-svh' : 'min-h-[28rem]', className, )} > <div className="mx-auto grid w-full max-w-5xl items-center gap-10 lg:grid-cols-2 lg:gap-16"> <div> {codeText ? <p className="text-muted-foreground text-sm font-medium">HTTP {codeText}</p> : null} {codeText ? <p className="mt-3 text-6xl font-semibold tracking-tighter sm:text-7xl">{codeText}</p> : null} <h1 className="mt-3 text-2xl font-semibold tracking-tight sm:text-3xl">{titleText}</h1> {descriptionText ? <p className="text-muted-foreground mt-4 max-w-md text-base">{descriptionText}</p> : null} {showPrimary || showSecondary ? ( <div className="mt-8 flex flex-wrap items-center gap-3"> {showPrimary ? ( primaryHref ? ( <Button asChild size="lg" disabled={primaryDisabled}> <a href={primaryHref} onClick={onPrimary}> <ArrowLeft /> {primaryText} </a> </Button> ) : ( <Button size="lg" disabled={primaryDisabled} onClick={onPrimary}> <ArrowLeft /> {primaryText} </Button> ) ) : null} {showSecondary ? ( secondaryHref ? ( <Button asChild size="lg" variant="outline" disabled={secondaryDisabled}> <a href={secondaryHref} onClick={onSecondary}> {secondaryText} <LifeBuoy /> </a> </Button> ) : ( <Button size="lg" variant="outline" disabled={secondaryDisabled} onClick={onSecondary}> {secondaryText} <LifeBuoy /> </Button> ) ) : null} </div> ) : null} </div> {image ? ( <div className="border-border bg-muted overflow-hidden rounded-xl border"> <img src={image} alt={imageAlt} className="aspect-[4/3] w-full object-cover" /> </div> ) : image === undefined ? ( <div className="border-border bg-muted/40 overflow-hidden rounded-xl border font-mono" aria-hidden="true"> <div className="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs"> curl -i https://app.acme.dev/pricing/teams </div> <div className="space-y-1 p-4 text-xs leading-relaxed"> <p> <span className="text-muted-foreground">HTTP/1.1</span> {codeText || '404'} Not Found </p> <p className="text-muted-foreground">content-type: application/json</p> <p className="text-muted-foreground">x-request-id: req_9k2e18</p> <p className="text-muted-foreground mt-3">{'{ "error": "not_found", "path": "/pricing/teams" }'}</p> </div> </div> ) : null} </div> </section> ) }components/blocks/error-page/Error500.tsx5.6 kB
'use client' import { LifeBuoy, RefreshCcw } from 'lucide-react' import { cn } from '@/lib/utils' import { Button } from '@/components/ui/button' export interface Error500Props { code?: string title?: string description?: string /** Replaces the HTTP response panel. Pass `''` to hide art entirely. */ image?: string imageAlt?: string primaryLabel?: string secondaryLabel?: string tertiaryLabel?: string primaryHref?: string secondaryHref?: string tertiaryHref?: string showPrimary?: boolean showSecondary?: boolean showTertiary?: boolean primaryDisabled?: boolean secondaryDisabled?: boolean tertiaryDisabled?: boolean onPrimary?: () => void onSecondary?: () => void onTertiary?: () => void /** `'page'` fills the viewport; `'contained'` fits a preview or nested panel. */ layout?: 'page' | 'contained' className?: string } const copy = (fallback: string, override?: string) => (override === undefined ? fallback : override) export function Error500({ code, title, description, image = '/illustrations/error-500.jpg', imageAlt = '', primaryLabel, secondaryLabel, tertiaryLabel, primaryHref, secondaryHref, tertiaryHref, showPrimary = true, showSecondary = true, showTertiary = true, primaryDisabled = false, secondaryDisabled = false, tertiaryDisabled = false, onPrimary, onSecondary, onTertiary, layout = 'page', className, }: Error500Props) { const codeText = copy('500', code) const titleText = copy('Something went wrong', title) const descriptionText = copy( 'We hit an unexpected error on our side. Try again in a moment — if it keeps happening, our team is on it.', description, ) const primaryText = copy('Try again', primaryLabel) const secondaryText = copy('Back to home', secondaryLabel) const tertiaryText = copy('Contact support', tertiaryLabel) return ( <section data-slot="error-500" data-layout={layout} className={cn( 'bg-background relative flex items-center overflow-hidden px-6 py-16', layout === 'page' ? 'min-h-svh' : 'min-h-[28rem]', className, )} > <div className="mx-auto grid w-full max-w-5xl items-center gap-10 lg:grid-cols-2 lg:gap-16"> <div> {codeText ? <p className="text-muted-foreground text-sm font-medium">HTTP {codeText}</p> : null} {codeText ? <p className="mt-3 text-6xl font-semibold tracking-tighter sm:text-7xl">{codeText}</p> : null} <h1 className="mt-3 text-2xl font-semibold tracking-tight sm:text-3xl">{titleText}</h1> {descriptionText ? <p className="text-muted-foreground mt-4 max-w-md text-base">{descriptionText}</p> : null} {showPrimary || showSecondary || showTertiary ? ( <div className="mt-8 flex flex-wrap items-center gap-3"> {showPrimary ? ( primaryHref ? ( <Button asChild size="lg" disabled={primaryDisabled}> <a href={primaryHref} onClick={onPrimary}> <RefreshCcw /> {primaryText} </a> </Button> ) : ( <Button size="lg" disabled={primaryDisabled} onClick={onPrimary}> <RefreshCcw /> {primaryText} </Button> ) ) : null} {showSecondary ? ( secondaryHref ? ( <Button asChild size="lg" variant="outline" disabled={secondaryDisabled}> <a href={secondaryHref} onClick={onSecondary}> {secondaryText} </a> </Button> ) : ( <Button size="lg" variant="outline" disabled={secondaryDisabled} onClick={onSecondary}> {secondaryText} </Button> ) ) : null} {showTertiary ? ( tertiaryHref ? ( <Button asChild size="lg" variant="ghost" disabled={tertiaryDisabled}> <a href={tertiaryHref} onClick={onTertiary}> {tertiaryText} <LifeBuoy /> </a> </Button> ) : ( <Button size="lg" variant="ghost" disabled={tertiaryDisabled} onClick={onTertiary}> {tertiaryText} <LifeBuoy /> </Button> ) ) : null} </div> ) : null} </div> {image ? ( <div className="border-border bg-muted overflow-hidden rounded-xl border"> <img src={image} alt={imageAlt} className="aspect-[4/3] w-full object-cover" /> </div> ) : image === undefined ? ( <div className="border-border bg-muted/40 overflow-hidden rounded-xl border font-mono" aria-hidden="true"> <div className="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs"> POST /api/v1/invoices </div> <div className="space-y-1 p-4 text-xs leading-relaxed"> <p> <span className="text-muted-foreground">HTTP/1.1</span>{' '} <span className="text-destructive">{codeText || '500'} Internal Server Error</span> </p> <p className="text-muted-foreground">error: ECONNRESET</p> <p className="text-muted-foreground">at: Worker.run:142</p> <p className="text-muted-foreground">x-request-id: req_8f3c2a</p> </div> </div> ) : null} </div> </section> ) }components/blocks/error-page/ErrorPages.tsx13.7 kB
'use client' import type { ComponentProps, ReactNode } from 'react' import { Clock, RotateCcw } from 'lucide-react' import { cn } from '@/lib/utils' import { Button } from '@/components/ui/button' 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' className?: 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 } const copy = (fallback: string, override?: string) => (override === undefined ? fallback : override) function Action({ href, onClick, children, ...buttonProps }: { href?: string onClick?: () => void children: ReactNode } & Omit<ComponentProps<typeof Button>, 'onClick'>) { if (href) { return ( <Button asChild {...buttonProps}> <a href={href} onClick={onClick}> {children} </a> </Button> ) } return ( <Button {...buttonProps} onClick={onClick}> {children} </Button> ) } export function ErrorPages({ variant = 'all', size = 'default', 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) { const pick = (v: ErrorPagesVariant, fallback: string, override?: string) => variant === v ? copy(fallback, override) : fallback const frameClass = 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 bodyClass = size === 'sm' ? 'p-5' : 'p-6' const codeClass = cn('font-semibold tracking-tighter', size === 'sm' ? 'text-2xl' : 'text-4xl') const titleClass = 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 = cn('flex flex-wrap items-center gap-2', size === 'sm' ? 'mt-4' : 'mt-5') const actionSize = size === 'sm' ? ('xs' as const) : ('sm' as const) const show = (v: ErrorPagesVariant) => variant === 'all' || variant === v const title404 = pick('404', 'Page not found', title) const desc404 = pick( '404', "The page you're looking for doesn't exist or was moved. Check the URL or head back home.", description, ) const code404 = pick('404', '404', code) const image404 = pick('404', '/illustrations/error-404.jpg', image) const primary404 = pick('404', 'Go home', primaryLabel) const secondary404 = pick('404', 'Search docs', secondaryLabel) const title500 = pick('500', 'Internal server error', title) const desc500 = pick( '500', 'Something went wrong on our end. The team has been notified — try again in a moment.', description, ) const code500 = pick('500', '500', code) const image500 = pick('500', '/illustrations/error-500.jpg', image) const primary500 = pick('500', 'Retry', primaryLabel) const secondary500 = pick('500', 'Status page', secondaryLabel) const title403 = pick('403', 'Access denied', title) const desc403 = pick( '403', "You don't have permission to view this page. Switch accounts or ask an admin to request access.", description, ) const code403 = pick('403', '403', code) const image403 = pick('403', '/illustrations/error-403.jpg', image) const primary403 = pick('403', 'Request access', primaryLabel) const secondary403 = pick('403', 'Switch account', secondaryLabel) const title503 = pick('maintenance', 'Scheduled maintenance', title) const desc503 = pick( 'maintenance', "We're upgrading our systems. The service will be back online shortly.", description, ) const code503 = pick('maintenance', '503', code) const image503 = pick('maintenance', '/illustrations/error-maintenance.jpg', image) const tag503 = pick('maintenance', 'Back online around 14:00 UTC', tag) const primary503 = pick('maintenance', 'Subscribe for updates', primaryLabel) const secondary503 = variant === 'maintenance' ? (secondaryLabel ?? '') : (secondaryLabel ?? '') return ( <div data-slot="error-pages" data-size={size} className={cn(variant === 'all' && 'grid gap-4 sm:grid-cols-2', className)} > {show('404') && ( <article className={frameClass} data-slot="error-page" data-variant="404" role="status"> {image404 ? ( <img src={image404} alt={imageAlt} className={imgClass} /> ) : image404 === undefined ? ( <div className="border-border bg-muted/40 border-b font-mono" aria-hidden="true"> <div className="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs"> GET /pricing/teams </div> <div className="space-y-1 p-3 text-xs leading-relaxed"> <p> <span className="text-muted-foreground">HTTP/1.1</span> {code404 || '404'} Not Found </p> <p className="text-muted-foreground">x-request-id: req_9k2e18</p> </div> </div> ) : null} <div className={bodyClass}> {code404 ? <p className={codeClass}>{code404}</p> : null} <h3 className={titleClass}>{title404}</h3> <p className={descClass}>{desc404}</p> {variant === '404' && tag ? ( <p className="text-muted-foreground mt-3 inline-flex items-center rounded-full border px-3 py-1 text-xs"> {tag} </p> ) : null} {showPrimary || showSecondary ? ( <div className={actionsClass}> {showPrimary ? ( <Action href={primaryHref} size={actionSize} disabled={primaryDisabled} onClick={onHome}> {primary404} </Action> ) : null} {showSecondary ? ( <Action href={secondaryHref} size={actionSize} variant="ghost" disabled={secondaryDisabled} onClick={onSearch} > {secondary404} </Action> ) : null} </div> ) : null} </div> </article> )} {show('500') && ( <article className={frameClass} data-slot="error-page" data-variant="500" role="alert"> {image500 ? ( <img src={image500} alt={imageAlt} className={imgClass} /> ) : image500 === undefined ? ( <div className="border-border bg-muted/40 border-b font-mono" aria-hidden="true"> <div className="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs"> POST /api/v1/invoices </div> <div className="space-y-1 p-3 text-xs leading-relaxed"> <p> <span className="text-muted-foreground">HTTP/1.1</span>{' '} <span className="text-destructive">{code500 || '500'} Internal Server Error</span> </p> <p className="text-muted-foreground">error: ECONNRESET at Worker.run:142</p> </div> </div> ) : null} <div className={bodyClass}> {code500 ? <p className={codeClass}>{code500}</p> : null} <h3 className={titleClass}>{title500}</h3> <p className={descClass}>{desc500}</p> {variant === '500' && tag ? ( <p className="text-muted-foreground mt-3 inline-flex items-center rounded-full border px-3 py-1 text-xs"> {tag} </p> ) : null} {showPrimary || showSecondary ? ( <div className={actionsClass}> {showPrimary ? ( <Action href={primaryHref} size={actionSize} disabled={primaryDisabled} onClick={onRetry}> <RotateCcw /> {primary500} </Action> ) : null} {showSecondary ? ( <Action href={secondaryHref} size={actionSize} variant="outline" disabled={secondaryDisabled} onClick={onStatus} > {secondary500} </Action> ) : null} </div> ) : null} </div> </article> )} {show('403') && ( <article className={frameClass} data-slot="error-page" data-variant="403" role="alert"> {image403 ? ( <img src={image403} alt={imageAlt} className={imgClass} /> ) : image403 === undefined ? ( <div className="border-border bg-muted/40 border-b font-mono" aria-hidden="true"> <div className="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs"> GET /admin/billing </div> <div className="space-y-1 p-3 text-xs leading-relaxed"> <p> <span className="text-muted-foreground">HTTP/1.1</span>{' '} <span className="text-warning">{code403 || '403'} Forbidden</span> </p> <p className="text-muted-foreground">scope billing:read — denied for guest</p> </div> </div> ) : null} <div className={bodyClass}> {code403 ? <p className={codeClass}>{code403}</p> : null} <h3 className={titleClass}>{title403}</h3> <p className={descClass}>{desc403}</p> {variant === '403' && tag ? ( <p className="text-muted-foreground mt-3 inline-flex items-center rounded-full border px-3 py-1 text-xs"> {tag} </p> ) : null} {showPrimary || showSecondary ? ( <div className={actionsClass}> {showSecondary ? ( <Action href={secondaryHref} size={actionSize} variant="outline" disabled={secondaryDisabled} onClick={onSwitchAccount} > {secondary403} </Action> ) : null} {showPrimary ? ( <Action href={primaryHref} size={actionSize} disabled={primaryDisabled} onClick={onRequestAccess}> {primary403} </Action> ) : null} </div> ) : null} </div> </article> )} {show('maintenance') && ( <article className={frameClass} data-slot="error-page" data-variant="maintenance" role="status"> {image503 ? ( <img src={image503} alt={imageAlt} className={imgClass} /> ) : image503 === undefined ? ( <div className="border-border bg-muted/40 border-b font-mono" aria-hidden="true"> <div className="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs">GET /health</div> <div className="space-y-1 p-3 text-xs leading-relaxed"> <p> <span className="text-muted-foreground">HTTP/1.1</span>{' '} <span className="text-warning">{code503 || '503'} Service Unavailable</span> </p> <p className="text-muted-foreground">Retry-After: 3600</p> </div> </div> ) : null} <div className={bodyClass}> {code503 ? <p className={codeClass}>{code503}</p> : null} <h3 className={titleClass}>{title503}</h3> <p className={descClass}>{desc503}</p> {tag503 ? ( <p className="text-muted-foreground mt-3 inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs"> <Clock className="size-3.5" aria-hidden="true" /> {tag503} </p> ) : null} {showPrimary || (showSecondary && secondary503) ? ( <div className={actionsClass}> {showPrimary ? ( <Action href={primaryHref} size={actionSize} variant="ghost" disabled={primaryDisabled} onClick={onSubscribe} > {primary503} </Action> ) : null} {showSecondary && secondary503 ? ( <Action href={secondaryHref} size={actionSize} variant="outline" disabled={secondaryDisabled} onClick={onStatus} > {secondary503} </Action> ) : null} </div> ) : null} </div> </article> )} </div> ) }
Raw manifest:https://uipkge.dev/r/react/error-page.json