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 uipkge-ng@latest add error-page$npx uipkge-ng@latest add error-page$yarn dlx uipkge-ng@latest add error-page$bunx uipkge-ng@latest add error-pageManifest:
https://uipkge.dev/r/angular/error-page.jsonInstalls to:src/app/components/blocks/error-page/Variants
Loading interactive previews…
Loading interactive previews…
npm dependencies
Includes
Files installed (4)
src/app/components/blocks/error-page/error-404.component.ts5.6 kB
// Full-page 404 shipped inside the error-page set. Same copy and HTTP panel as the // standalone error-404 block, with an error-page-scoped selector. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core' import { ArrowLeft, LifeBuoy, LucideAngularModule } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiButtonComponent } from '@/ui/button/button.component' @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-error-page-404, [ui-error-page-404]', standalone: true, // React renders the root <section> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [LucideAngularModule, UiButtonComponent], template: ` <section data-slot="error-404" [attr.data-layout]="layout" [class]="rootClass"> <div class="mx-auto grid w-full max-w-5xl items-center gap-10 lg:grid-cols-2 lg:gap-16"> <div> @if (codeText) { <p class="text-muted-foreground text-sm font-medium">HTTP {{ codeText }}</p> } @if (codeText) { <p class="mt-3 text-6xl font-semibold tracking-tighter sm:text-7xl">{{ codeText }}</p> } <h1 class="mt-3 text-2xl font-semibold tracking-tight sm:text-3xl">{{ titleText }}</h1> @if (descriptionText) { <p class="text-muted-foreground mt-4 max-w-md text-base">{{ descriptionText }}</p> } @if (showPrimary || showSecondary) { <div class="mt-8 flex flex-wrap items-center gap-3"> @if (showPrimary) { @if (primaryHref) { <a ui-button size="lg" [href]="primaryHref" (click)="primary.emit()"> <lucide-icon [img]="ArrowLeft" /> {{ primaryText }} </a> } @else { <button ui-button size="lg" [disabled]="primaryDisabled" (click)="primary.emit()"> <lucide-icon [img]="ArrowLeft" /> {{ primaryText }} </button> } } @if (showSecondary) { @if (secondaryHref) { <a ui-button size="lg" variant="outline" [href]="secondaryHref" (click)="secondary.emit()"> {{ secondaryText }} <lucide-icon [img]="LifeBuoy" /> </a> } @else { <button ui-button size="lg" variant="outline" [disabled]="secondaryDisabled" (click)="secondary.emit()" > {{ secondaryText }} <lucide-icon [img]="LifeBuoy" /> </button> } } </div> } </div> @if (image) { <div class="border-border bg-muted overflow-hidden rounded-xl border"> <img [src]="image" [alt]="imageAlt" class="aspect-[4/3] w-full object-cover" /> </div> } @else if (image === undefined) { <div class="border-border bg-muted/40 overflow-hidden rounded-xl border font-mono" aria-hidden="true"> <div class="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs"> curl -i https://app.acme.dev/pricing/teams </div> <div class="space-y-1 p-4 text-xs leading-relaxed"> <p><span class="text-muted-foreground">HTTP/1.1</span> {{ codeText || '404' }} Not Found</p> <p class="text-muted-foreground">content-type: application/json</p> <p class="text-muted-foreground">x-request-id: req_9k2e18</p> <p class="text-muted-foreground mt-3">{{ '{ "error": "not_found", "path": "/pricing/teams" }' }}</p> </div> </div> } </div> </section> `, }) export class UiErrorPage404Component { protected readonly ArrowLeft = ArrowLeft protected readonly LifeBuoy = LifeBuoy @Input() code?: string @Input() title?: string @Input() description?: string /** Replaces the HTTP response panel. Pass `''` to hide art entirely. */ @Input() image: string | undefined = '/illustrations/error-404.jpg' @Input() imageAlt = '' @Input() primaryLabel?: string @Input() secondaryLabel?: string @Input() primaryHref?: string @Input() secondaryHref?: string @Input() showPrimary = true @Input() showSecondary = true @Input() primaryDisabled = false @Input() secondaryDisabled = false @Output() primary = new EventEmitter<void>() @Output() secondary = new EventEmitter<void>() /** `'page'` fills the viewport; `'contained'` fits a preview or nested panel. */ @Input() layout: 'page' | 'contained' = 'page' @Input('class') className?: string get codeText(): string { return this.code === undefined ? '404' : this.code } get titleText(): string { return this.title === undefined ? 'Page not found' : this.title } get descriptionText(): string { return this.description === undefined ? "The page you're looking for doesn't exist or may have been moved. Check the URL or head back home." : this.description } get primaryText(): string { return this.primaryLabel === undefined ? 'Back to home' : this.primaryLabel } get secondaryText(): string { return this.secondaryLabel === undefined ? 'Contact support' : this.secondaryLabel } get rootClass(): string { return cn( 'bg-background relative flex items-center overflow-hidden px-6 py-16', this.layout === 'page' ? 'min-h-svh' : 'min-h-[28rem]', this.className, ) } }src/app/components/blocks/error-page/error-500.component.ts6.5 kB
// Full-page 500 shipped inside the error-page set. Same copy and HTTP panel as the // standalone error-500 block, with an error-page-scoped selector. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core' import { LifeBuoy, LucideAngularModule, RefreshCcw } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiButtonComponent } from '@/ui/button/button.component' @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-error-page-500, [ui-error-page-500]', standalone: true, // React renders the root <section> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [LucideAngularModule, UiButtonComponent], template: ` <section data-slot="error-500" [attr.data-layout]="layout" [class]="rootClass"> <div class="mx-auto grid w-full max-w-5xl items-center gap-10 lg:grid-cols-2 lg:gap-16"> <div> @if (codeText) { <p class="text-muted-foreground text-sm font-medium">HTTP {{ codeText }}</p> } @if (codeText) { <p class="mt-3 text-6xl font-semibold tracking-tighter sm:text-7xl">{{ codeText }}</p> } <h1 class="mt-3 text-2xl font-semibold tracking-tight sm:text-3xl">{{ titleText }}</h1> @if (descriptionText) { <p class="text-muted-foreground mt-4 max-w-md text-base">{{ descriptionText }}</p> } @if (showPrimary || showSecondary || showTertiary) { <div class="mt-8 flex flex-wrap items-center gap-3"> @if (showPrimary) { @if (primaryHref) { <a ui-button size="lg" [href]="primaryHref" (click)="primary.emit()"> <lucide-icon [img]="RefreshCcw" /> {{ primaryText }} </a> } @else { <button ui-button size="lg" [disabled]="primaryDisabled" (click)="primary.emit()"> <lucide-icon [img]="RefreshCcw" /> {{ primaryText }} </button> } } @if (showSecondary) { @if (secondaryHref) { <a ui-button size="lg" variant="outline" [href]="secondaryHref" (click)="secondary.emit()"> {{ secondaryText }} </a> } @else { <button ui-button size="lg" variant="outline" [disabled]="secondaryDisabled" (click)="secondary.emit()" > {{ secondaryText }} </button> } } @if (showTertiary) { @if (tertiaryHref) { <a ui-button size="lg" variant="ghost" [href]="tertiaryHref" (click)="tertiary.emit()"> {{ tertiaryText }} <lucide-icon [img]="LifeBuoy" /> </a> } @else { <button ui-button size="lg" variant="ghost" [disabled]="tertiaryDisabled" (click)="tertiary.emit()"> {{ tertiaryText }} <lucide-icon [img]="LifeBuoy" /> </button> } } </div> } </div> @if (image) { <div class="border-border bg-muted overflow-hidden rounded-xl border"> <img [src]="image" [alt]="imageAlt" class="aspect-[4/3] w-full object-cover" /> </div> } @else if (image === undefined) { <div class="border-border bg-muted/40 overflow-hidden rounded-xl border font-mono" 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-4 text-xs leading-relaxed"> <p> <span class="text-muted-foreground">HTTP/1.1</span> {{ ' ' }} <span class="text-destructive">{{ codeText || '500' }} Internal Server Error</span> </p> <p class="text-muted-foreground">error: ECONNRESET</p> <p class="text-muted-foreground">at: Worker.run:142</p> <p class="text-muted-foreground">x-request-id: req_8f3c2a</p> </div> </div> } </div> </section> `, }) export class UiErrorPage500Component { protected readonly LifeBuoy = LifeBuoy protected readonly RefreshCcw = RefreshCcw @Input() code?: string @Input() title?: string @Input() description?: string /** Replaces the HTTP response panel. Pass `''` to hide art entirely. */ @Input() image: string | undefined = '/illustrations/error-500.jpg' @Input() imageAlt = '' @Input() primaryLabel?: string @Input() secondaryLabel?: string @Input() tertiaryLabel?: string @Input() primaryHref?: string @Input() secondaryHref?: string @Input() tertiaryHref?: string @Input() showPrimary = true @Input() showSecondary = true @Input() showTertiary = true @Input() primaryDisabled = false @Input() secondaryDisabled = false @Input() tertiaryDisabled = false @Output() primary = new EventEmitter<void>() @Output() secondary = new EventEmitter<void>() @Output() tertiary = new EventEmitter<void>() /** `'page'` fills the viewport; `'contained'` fits a preview or nested panel. */ @Input() layout: 'page' | 'contained' = 'page' @Input('class') className?: string get codeText(): string { return this.code === undefined ? '500' : this.code } get titleText(): string { return this.title === undefined ? 'Something went wrong' : this.title } get descriptionText(): string { return this.description === undefined ? 'We hit an unexpected error on our side. Try again in a moment — if it keeps happening, our team is on it.' : this.description } get primaryText(): string { return this.primaryLabel === undefined ? 'Try again' : this.primaryLabel } get secondaryText(): string { return this.secondaryLabel === undefined ? 'Back to home' : this.secondaryLabel } get tertiaryText(): string { return this.tertiaryLabel === undefined ? 'Contact support' : this.tertiaryLabel } get rootClass(): string { return cn( 'bg-background relative flex items-center overflow-hidden px-6 py-16', this.layout === 'page' ? 'min-h-svh' : 'min-h-[28rem]', this.className, ) } }src/app/components/blocks/error-page/error-pages.component.ts17.4 kB
// Four-variant status-screen gallery: 404, 500, 403, and maintenance cards with matching // HTTP response panels, plus single-variant mode. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core' import { Clock, LucideAngularModule, RotateCcw } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiButtonComponent } from '@/ui/button/button.component' export type ErrorPagesVariant = '404' | '500' | '403' | 'maintenance' @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-error-pages, [ui-error-pages]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [LucideAngularModule, UiButtonComponent], template: ` <div data-slot="error-pages" [attr.data-size]="size" [class]="rootClass"> @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> } <div [class]="bodyClass"> @if (code404) { <p [class]="codeClass">{{ code404 }}</p> } <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 (showPrimary || showSecondary) { <div [class]="actionsClass"> @if (showPrimary) { @if (primaryHref) { <a ui-button [size]="actionSize" [href]="primaryHref" (click)="home.emit()">{{ primary404 }}</a> } @else { <button ui-button [size]="actionSize" [disabled]="primaryDisabled" (click)="home.emit()"> {{ primary404 }} </button> } } @if (showSecondary) { @if (secondaryHref) { <a ui-button [size]="actionSize" variant="ghost" [href]="secondaryHref" (click)="search.emit()"> {{ secondary404 }} </a> } @else { <button ui-button [size]="actionSize" variant="ghost" [disabled]="secondaryDisabled" (click)="search.emit()" > {{ secondary404 }} </button> } } </div> } </div> </article> } @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> } <div [class]="bodyClass"> @if (code500) { <p [class]="codeClass">{{ code500 }}</p> } <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 (showPrimary || showSecondary) { <div [class]="actionsClass"> @if (showPrimary) { @if (primaryHref) { <a ui-button [size]="actionSize" [href]="primaryHref" (click)="retry.emit()"> <lucide-icon [img]="RotateCcw" /> {{ primary500 }} </a> } @else { <button ui-button [size]="actionSize" [disabled]="primaryDisabled" (click)="retry.emit()"> <lucide-icon [img]="RotateCcw" /> {{ primary500 }} </button> } } @if (showSecondary) { @if (secondaryHref) { <a ui-button [size]="actionSize" variant="outline" [href]="secondaryHref" (click)="status.emit()"> {{ secondary500 }} </a> } @else { <button ui-button [size]="actionSize" variant="outline" [disabled]="secondaryDisabled" (click)="status.emit()" > {{ secondary500 }} </button> } } </div> } </div> </article> } @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> } <div [class]="bodyClass"> @if (code403) { <p [class]="codeClass">{{ code403 }}</p> } <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 (showPrimary || showSecondary) { <div [class]="actionsClass"> @if (showSecondary) { @if (secondaryHref) { <a ui-button [size]="actionSize" variant="outline" [href]="secondaryHref" (click)="switchAccount.emit()" > {{ secondary403 }} </a> } @else { <button ui-button [size]="actionSize" variant="outline" [disabled]="secondaryDisabled" (click)="switchAccount.emit()" > {{ secondary403 }} </button> } } @if (showPrimary) { @if (primaryHref) { <a ui-button [size]="actionSize" [href]="primaryHref" (click)="requestAccess.emit()"> {{ primary403 }} </a> } @else { <button ui-button [size]="actionSize" [disabled]="primaryDisabled" (click)="requestAccess.emit()"> {{ primary403 }} </button> } } </div> } </div> </article> } @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> } <div [class]="bodyClass"> @if (code503) { <p [class]="codeClass">{{ code503 }}</p> } <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" > <lucide-icon [img]="Clock" class="size-3.5" aria-hidden="true" /> {{ tag503 }} </p> } @if (showPrimary || (showSecondary && secondary503)) { <div [class]="actionsClass"> @if (showPrimary) { @if (primaryHref) { <a ui-button [size]="actionSize" variant="ghost" [href]="primaryHref" (click)="subscribe.emit()"> {{ primary503 }} </a> } @else { <button ui-button [size]="actionSize" variant="ghost" [disabled]="primaryDisabled" (click)="subscribe.emit()" > {{ primary503 }} </button> } } @if (showSecondary && secondary503) { @if (secondaryHref) { <a ui-button [size]="actionSize" variant="outline" [href]="secondaryHref" (click)="status.emit()"> {{ secondary503 }} </a> } @else { <button ui-button [size]="actionSize" variant="outline" [disabled]="secondaryDisabled" (click)="status.emit()" > {{ secondary503 }} </button> } } </div> } </div> </article> } </div> `, }) export class UiErrorPagesComponent { protected readonly Clock = Clock protected readonly RotateCcw = RotateCcw /** `'all'` renders the full gallery; a single variant renders just that screen. */ @Input() variant: ErrorPagesVariant | 'all' = 'all' @Input() size: 'sm' | 'default' = 'default' @Input('class') className?: string @Input() title?: string @Input() description?: string /** Status numeral. Pass `''` to hide. */ @Input() code?: string /** Eyebrow pill (maintenance ETA by default). Pass `''` to hide. */ @Input() tag?: string /** Replaces the HTTP response panel on a single variant. Pass `''` to hide art. */ @Input() image?: string @Input() imageAlt = '' @Input() primaryLabel?: string @Input() secondaryLabel?: string @Input() primaryHref?: string @Input() secondaryHref?: string @Input() showPrimary = true @Input() showSecondary = true @Input() primaryDisabled = false @Input() secondaryDisabled = false @Output() home = new EventEmitter<void>() @Output() search = new EventEmitter<void>() @Output() retry = new EventEmitter<void>() @Output() status = new EventEmitter<void>() @Output() switchAccount = new EventEmitter<void>() @Output() requestAccess = new EventEmitter<void>() @Output() subscribe = new EventEmitter<void>() show(v: ErrorPagesVariant): boolean { return this.variant === 'all' || this.variant === v } private copy(fallback: string, override?: string): string { return override === undefined ? fallback : override } private forVariant(v: ErrorPagesVariant, fallback: string, override?: string): string { return this.variant === v ? this.copy(fallback, override) : fallback } get rootClass(): string { return cn(this.variant === 'all' && 'grid gap-4 sm:grid-cols-2', this.className) } get frameClass(): string { return cn('border-border bg-card overflow-hidden rounded-xl border text-left', this.size === 'sm' && 'rounded-lg') } get imgClass(): string { return 'aspect-[16/10] w-full object-cover' } get panelClass(): string { return 'border-border bg-muted/40 border-b font-mono' } get bodyClass(): string { return this.size === 'sm' ? 'p-5' : 'p-6' } get codeClass(): string { return cn('font-semibold tracking-tighter', this.size === 'sm' ? 'text-2xl' : 'text-4xl') } get titleClass(): string { return cn('mt-1.5 font-semibold tracking-tight', this.size === 'sm' ? 'text-base' : 'text-xl') } get descClass(): string { return 'text-muted-foreground mt-2 max-w-sm text-sm' } get actionsClass(): string { return cn('flex flex-wrap items-center gap-2', this.size === 'sm' ? 'mt-4' : 'mt-5') } get actionSize(): 'xs' | 'sm' { return this.size === 'sm' ? 'xs' : 'sm' } get title404(): string { return this.forVariant('404', 'Page not found', this.title) } get desc404(): string { return this.forVariant( '404', "The page you're looking for doesn't exist or was moved. Check the URL or head back home.", this.description, ) } get code404(): string { return this.forVariant('404', '404', this.code) } get image404(): string | undefined { return this.forVariant('404', '/illustrations/error-404.jpg', this.image) } get primary404(): string { return this.forVariant('404', 'Go home', this.primaryLabel) } get secondary404(): string { return this.forVariant('404', 'Search docs', this.secondaryLabel) } get title500(): string { return this.forVariant('500', 'Internal server error', this.title) } get desc500(): string { return this.forVariant( '500', 'Something went wrong on our end. The team has been notified — try again in a moment.', this.description, ) } get code500(): string { return this.forVariant('500', '500', this.code) } get image500(): string | undefined { return this.forVariant('500', '/illustrations/error-500.jpg', this.image) } get primary500(): string { return this.forVariant('500', 'Retry', this.primaryLabel) } get secondary500(): string { return this.forVariant('500', 'Status page', this.secondaryLabel) } get title403(): string { return this.forVariant('403', 'Access denied', this.title) } get desc403(): string { return this.forVariant( '403', "You don't have permission to view this page. Switch accounts or ask an admin to request access.", this.description, ) } get code403(): string { return this.forVariant('403', '403', this.code) } get image403(): string | undefined { return this.forVariant('403', '/illustrations/error-403.jpg', this.image) } get primary403(): string { return this.forVariant('403', 'Request access', this.primaryLabel) } get secondary403(): string { return this.forVariant('403', 'Switch account', this.secondaryLabel) } get title503(): string { return this.forVariant('maintenance', 'Scheduled maintenance', this.title) } get desc503(): string { return this.forVariant( 'maintenance', "We're upgrading our systems. The service will be back online shortly.", this.description, ) } get code503(): string { return this.forVariant('maintenance', '503', this.code) } get image503(): string | undefined { return this.forVariant('maintenance', '/illustrations/error-maintenance.jpg', this.image) } get tag503(): string { return this.forVariant('maintenance', 'Back online around 14:00 UTC', this.tag) } get primary503(): string { return this.forVariant('maintenance', 'Subscribe for updates', this.primaryLabel) } get secondary503(): string { return this.variant === 'maintenance' ? (this.secondaryLabel ?? '') : (this.secondaryLabel ?? '') } }src/app/components/blocks/error-page/index.ts0.2 kB
export { UiErrorPage404Component } from './error-404.component' export { UiErrorPage500Component } from './error-500.component' export { UiErrorPagesComponent, type ErrorPagesVariant } from './error-pages.component'
Raw manifest:https://uipkge.dev/r/angular/error-page.json