Skip to content

500 section

A server failure with a reference code and a real retry button.

Statesstartererror500failurereference

Live preview

full widthLive preview — open it in a new tab for the full-height version.
Open the preview in a new tab

Source

This exact file renders the preview above.

'use client'

import { RefreshCw, LifeBuoy } from 'lucide-react'
import { Button, ButtonLink } from '@/components/ui/button'
import { Container } from '@/components/ui/layout'
import { Alert } from '@/components/ui/alert'

/**
 * 500 section
 *
 * The distinguishing feature of a good server-error page is the reference code:
 * it is the only thing that makes a support conversation productive. Retry is a
 * real button rather than an instruction to refresh.
 */
export default function ServerErrorSection() {
  const reference = 'ERR-7F3A-2026'

  return (
    <section className="bg-canvas py-section">
      <Container size="narrow" className="text-center">
        <p className="display-type text-6xl font-semibold text-ink-subtle tabular-nums">500</p>
        <h1 className="display-type mt-4 text-2xl font-semibold text-ink-strong sm:text-3xl">
          Something went wrong on our side.
        </h1>
        <p className="mx-auto mt-3 max-w-md text-md text-ink-muted">
          This is not something you did. The team has been notified automatically.
        </p>

        <div className="mt-8 flex flex-wrap items-center justify-center gap-3">
          <Button
            onClick={() => window.location.reload()}
            leadingIcon={<RefreshCw className="size-4" />}
          >
            Try again
          </Button>
          <ButtonLink
            href="/forms/support-request"
            variant="outline"
            leadingIcon={<LifeBuoy className="size-4" />}
          >
            Contact support
          </ButtonLink>
        </div>

        <Alert tone="neutral" title="Reference code" className="mx-auto mt-10 max-w-md text-left">
          Quote <code className="font-mono font-semibold text-ink-strong">{reference}</code> if you
          contact support. It identifies this exact failure in the logs.
        </Alert>
      </Container>
    </section>
  )
}

components/blocks/sections/error/server-error.tsx

'use client'

import { RefreshCw, LifeBuoy } from 'lucide-react'
import { Button, ButtonLink } from '@/components/ui/button'
import { Container } from '@/components/ui/layout'
import { Alert } from '@/components/ui/alert'

/**
 * 500 section
 *
 * The distinguishing feature of a good server-error page is the reference code:
 * it is the only thing that makes a support conversation productive. Retry is a
 * real button rather than an instruction to refresh.
 */
export default function ServerErrorSection() {
  const reference = 'ERR-7F3A-2026'

  return (
    <section className="bg-canvas py-section">
      <Container size="narrow" className="text-center">
        <p className="display-type text-6xl font-semibold text-ink-subtle tabular-nums">500</p>
        <h1 className="display-type mt-4 text-2xl font-semibold text-ink-strong sm:text-3xl">
          Something went wrong on our side.
        </h1>
        <p className="mx-auto mt-3 max-w-md text-md text-ink-muted">
          This is not something you did. The team has been notified automatically.
        </p>

        <div className="mt-8 flex flex-wrap items-center justify-center gap-3">
          <Button
            onClick={() => window.location.reload()}
            leadingIcon={<RefreshCw className="size-4" />}
          >
            Try again
          </Button>
          <ButtonLink
            href="/forms/support-request"
            variant="outline"
            leadingIcon={<LifeBuoy className="size-4" />}
          >
            Contact support
          </ButtonLink>
        </div>

        <Alert tone="neutral" title="Reference code" className="mx-auto mt-10 max-w-md text-left">
          Quote <code className="font-mono font-semibold text-ink-strong">{reference}</code> if you
          contact support. It identifies this exact failure in the logs.
        </Alert>
      </Container>
    </section>
  )
}

components/ui/alert.tsx

import type { ReactNode } from 'react'
import { Info, CheckCircle2, AlertTriangle, OctagonAlert } from 'lucide-react'
import { cn } from '@/lib/cn'

/**
 * Alert
 *
 * A block-level message attached to a region of the page. The icon is chosen
 * from the tone, and `role` switches to `alert` for danger so screen readers
 * interrupt — informational tones use the polite `status` role instead.
 */
export type AlertTone = 'info' | 'success' | 'warning' | 'danger' | 'neutral'

export interface AlertProps {
  tone?: AlertTone
  title?: ReactNode
  children?: ReactNode
  /** Rendered under the body — usually one or two buttons. */
  actions?: ReactNode
  /** Slot for a dismiss control supplied by the caller. */
  trailing?: ReactNode
  className?: string
  /** Force the live-region politeness rather than deriving it from tone. */
  live?: 'off' | 'polite' | 'assertive'
}

const tones = {
  info: { icon: Info, surface: 'bg-info-soft border-info-line', accent: 'text-info' },
  success: {
    icon: CheckCircle2,
    surface: 'bg-success-soft border-success-line',
    accent: 'text-success',
  },
  warning: {
    icon: AlertTriangle,
    surface: 'bg-warning-soft border-warning-line',
    accent: 'text-warning',
  },
  danger: {
    icon: OctagonAlert,
    surface: 'bg-danger-soft border-danger-line',
    accent: 'text-danger',
  },
  neutral: { icon: Info, surface: 'bg-surface-sunken border-line', accent: 'text-ink-muted' },
} as const

export function Alert({
  tone = 'info',
  title,
  children,
  actions,
  trailing,
  className,
  live,
}: AlertProps) {
  const entry = tones[tone]
  const Icon = entry.icon
  const politeness = live ?? (tone === 'danger' ? 'assertive' : 'polite')

  return (
    <div
      role={tone === 'danger' ? 'alert' : 'status'}
      aria-live={politeness === 'off' ? undefined : politeness}
      className={cn('flex gap-3 rounded-md border p-3 text-sm', entry.surface, className)}
    >
      <Icon className={cn('mt-0.5 size-4 shrink-0', entry.accent)} aria-hidden="true" />
      <div className="min-w-0 flex-1">
        {title ? <p className="font-semibold text-ink-strong">{title}</p> : null}
        {children ? <div className={cn('text-ink-muted', title && 'mt-1')}>{children}</div> : null}
        {actions ? <div className="mt-3 flex flex-wrap gap-2">{actions}</div> : null}
      </div>
      {trailing ? <div className="shrink-0">{trailing}</div> : null}
    </div>
  )
}

Demo source — adapt to your project. Foundry is not published as a package.

Usage

The reference code is the only thing that makes a support conversation productive, and retry is a real button rather than an instruction to refresh.

  • Include a reference code even in a demo — its absence is the most common real-world omission.
  • Say explicitly that it is not the user's fault.

Variants and states

Every entry below is a genuine difference in behaviour or layout, and every one of them is visible in the preview above.

  • Status code
  • Retry action
  • Reference code

Accessibility

Real retry
The retry action is a button, not text telling the user to press F5.
Monospace reference
The code is set in a monospace face so digits are unambiguous.

Foundry implements published ARIA patterns and is tested against them. No WCAG certification is claimed — see the accessibility documentation for what is and is not covered.