Menu

Change language

GitHub repo

500 Server Error

blocklayout

Full-page 500 with an isometric crashed-server illustration, HTTP code, headline, and Try again + Back to home + Contact support. Prop-driven: pass title, description, image, action labels, hrefs, and layout (`page` | `contained`). Pass `image=""` to hide the art.

Installation

$npx shadcn@latest add https://uipkge.dev/r/lit/error-500.json
Manifest:https://uipkge.dev/r/lit/error-500.jsonInstalls to:src/uip/blocks/error-500/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
codestring—optional
headingstring—optional
descriptionstring—optional
imagestring/illustrations/error-500.jpgoptional
image-altstring—optional
primary-labelstring—optional
secondary-labelstring—optional
tertiary-labelstring—optional
primary-hrefstring—optional
secondary-hrefstring—optional
tertiary-hrefstring—optional
show-primarybooleantrueoptional
show-secondarybooleantrueoptional
show-tertiarybooleantrueoptional
primary-disabledbooleanfalseoptional
secondary-disabledbooleanfalseoptional
tertiary-disabledbooleanfalseoptional
layout'page' | 'contained'pageoptional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

HTMLElementTagNameMap
interface HTMLElementTagNameMap {
    'uip-error-500': UipError500
  }
}

npm dependencies

Files installed (1)

  • src/uip/blocks/error-500/error-500.ts6.6 kB
    import { LitElement, css, html, nothing } from 'lit'
    import { LifeBuoy, RefreshCcw } from 'lucide'
    import { cn } from '../../lib/utils'
    import { tailwind } from '../../lib/styles'
    import { ThemeController } from '../../lib/theme'
    import { icon } from '../../lib/icon'
    // Side-effect import: registers <uip-button> for standalone use (consumers
    // who install only this block still get a working button).
    import '../../components/button/button'
    
    const falseConverter = { fromAttribute: (v: string | null) => v !== null && v !== 'false' }
    
    /**
     * <uip-error-500> — the registry Error500 block as a web component.
     *
     * Class strings are React's Error500 verbatim. `heading` is React's `title`
     * (`title` is taken by HTMLElement). `onPrimary` / `onSecondary` /
     * `onTertiary` become `primary` / `secondary` / `tertiary` CustomEvents
     * (bubbles, composed).
     *
     * Deviation: React's `image === undefined` HTTP-panel branch is unreachable
     * (the default param fills `image` first), so it is not ported: omit `image`
     * for the default art, pass `image=""` to hide it.
     */
    export class UipError500 extends LitElement {
      static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }
      // :host display can't be a utility class (the host has no template of its own).
      static styles = [tailwind, css`:host { display: block; }`]
    
      static properties = {
        code: {},
        heading: {},
        description: {},
        image: {},
        imageAlt: { attribute: 'image-alt' },
        primaryLabel: { attribute: 'primary-label' },
        secondaryLabel: { attribute: 'secondary-label' },
        tertiaryLabel: { attribute: 'tertiary-label' },
        primaryHref: { attribute: 'primary-href' },
        secondaryHref: { attribute: 'secondary-href' },
        tertiaryHref: { attribute: 'tertiary-href' },
        showPrimary: { type: Boolean, attribute: 'show-primary', converter: falseConverter },
        showSecondary: { type: Boolean, attribute: 'show-secondary', converter: falseConverter },
        showTertiary: { type: Boolean, attribute: 'show-tertiary', converter: falseConverter },
        primaryDisabled: { type: Boolean, attribute: 'primary-disabled' },
        secondaryDisabled: { type: Boolean, attribute: 'secondary-disabled' },
        tertiaryDisabled: { type: Boolean, attribute: 'tertiary-disabled' },
        layout: { reflect: true },
      }
    
      code?: string
      heading?: string
      description?: string
      image = '/illustrations/error-500.jpg'
      imageAlt = ''
      primaryLabel?: string
      secondaryLabel?: string
      tertiaryLabel?: string
      primaryHref?: string
      secondaryHref?: string
      tertiaryHref?: string
      showPrimary = true
      showSecondary = true
      showTertiary = true
      primaryDisabled = false
      secondaryDisabled = false
      tertiaryDisabled = false
      layout: 'page' | 'contained' = 'page'
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'error-500')
        this.setAttribute('data-layout', this.layout)
      }
    
      willUpdate() {
        this.setAttribute('data-layout', this.layout)
      }
    
      private emit(name: 'primary' | 'secondary' | 'tertiary') {
        this.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true }))
      }
    
      render() {
        const codeText = this.code ?? '500'
        const titleText = this.heading ?? 'Something went wrong'
        const descriptionText =
          this.description ??
          'We hit an unexpected error on our side. Try again in a moment — if it keeps happening, our team is on it.'
        const primaryText = this.primaryLabel ?? 'Try again'
        const secondaryText = this.secondaryLabel ?? 'Back to home'
        const tertiaryText = this.tertiaryLabel ?? 'Contact support'
        return html`
          <section
            data-slot="error-500"
            data-layout=${this.layout}
            class=${cn(
              'bg-background relative flex items-center overflow-hidden px-6 py-16',
              this.layout === 'page' ? 'min-h-svh' : 'min-h-[28rem]',
            )}
          >
            <div class="mx-auto grid w-full max-w-5xl items-center gap-10 lg:grid-cols-2 lg:gap-16">
              <div>
                ${codeText
                  ? html`<p class="text-muted-foreground text-sm font-medium">HTTP ${codeText}</p>
                      <p class="mt-3 text-6xl font-semibold tracking-tighter sm:text-7xl">${codeText}</p>`
                  : nothing}
                <h1 class="mt-3 text-2xl font-semibold tracking-tight sm:text-3xl">${titleText}</h1>
                ${descriptionText
                  ? html`<p class="text-muted-foreground mt-4 max-w-md text-base">${descriptionText}</p>`
                  : nothing}
                ${this.showPrimary || this.showSecondary || this.showTertiary
                  ? html`<div class="mt-8 flex flex-wrap items-center gap-3">
                      ${this.showPrimary
                        ? html`<uip-button
                            size="lg"
                            ?disabled=${this.primaryDisabled}
                            href=${this.primaryHref ?? nothing}
                            @click=${() => this.emit('primary')}
                          >
                            ${icon(RefreshCcw, 'refresh-ccw')} ${primaryText}
                          </uip-button>`
                        : nothing}
                      ${this.showSecondary
                        ? html`<uip-button
                            size="lg"
                            variant="outline"
                            ?disabled=${this.secondaryDisabled}
                            href=${this.secondaryHref ?? nothing}
                            @click=${() => this.emit('secondary')}
                          >
                            ${secondaryText}
                          </uip-button>`
                        : nothing}
                      ${this.showTertiary
                        ? html`<uip-button
                            size="lg"
                            variant="ghost"
                            ?disabled=${this.tertiaryDisabled}
                            href=${this.tertiaryHref ?? nothing}
                            @click=${() => this.emit('tertiary')}
                          >
                            ${tertiaryText} ${icon(LifeBuoy, 'life-buoy')}
                          </uip-button>`
                        : nothing}
                    </div>`
                  : nothing}
              </div>
              ${this.image
                ? html`<div class="border-border bg-muted overflow-hidden rounded-xl border">
                    <img src=${this.image} alt=${this.imageAlt} class="aspect-[4/3] w-full object-cover" />
                  </div>`
                : nothing}
            </div>
          </section>
        `
      }
    }
    
    customElements.get('uip-error-500') || customElements.define('uip-error-500', UipError500)
    
    declare global {
      interface HTMLElementTagNameMap {
        'uip-error-500': UipError500
      }
    }
    

Raw manifest:https://uipkge.dev/r/lit/error-500.json