# ErrorPage

> Full-page states for 404, 500, offline, maintenance, no access, unknown workspace and a module that is coming soon, with sensible actions, an error ID to quote and English and Arabic copy.

Source: https://docs.nasaqui.com/components/error-pages

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/error-pages.json
```

The screens a product shows when it cannot show what was asked for. One `ErrorPage` takes a `kind`, and seven named
wrappers (`NotFoundPage`, `ServerErrorPage`, `OfflinePage`, `MaintenancePage`, `ForbiddenPage`,
`UnknownWorkspacePage`, `ComingSoonPage`) save you typing it. Each says what happened, what to do next and offers the
right buttons. It never shows a stack trace. It is presentational: it takes handlers and never navigates itself.

## When to use

- Your router's not-found and error boundaries, the offline fallback, the maintenance switch.
- A workspace URL that does not match, or a module behind a coming-soon flag.

## When not to use

- An empty list or a failed panel inside a page: use [`EmptyState` and `ErrorState`](https://docs.nasaqui.com/components/states).
- A notice above content that still works: use [`Alert`](https://docs.nasaqui.com/components/alert).

## Import

```tsx
import { NotFoundPage } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { NotFoundPage, ServerErrorPage } from "@fadymondy/nasaq/web";

export const NotFound = () => <NotFoundPage onHome={() => router.push("/")} onBack={() => router.back()} />;

export const Crashed = ({ id }: { id: string }) => (
  <ServerErrorPage errorId={id} onRetry={() => router.refresh()} onContactSupport={() => openChat()} />
);
```

## Anatomy

```
ErrorPage            data-slot="error-page" (data-kind), a <main>
├─ logo              ProductLogo of the provider brand, or your own
├─ icon tile
├─ status code       data-slot="error-page-code", always LTR
├─ title + text      the h1 and the sentence
├─ extras            maintenance ETA, error ID with copy, "we will tell you" confirmation
└─ actions           defaults per kind, or your own
```

## API

Every `main` prop except `title` and `children`, plus:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `kind` | `"not-found" \| "server-error" \| "offline" \| "maintenance" \| "forbidden" \| "unknown-workspace" \| "coming-soon"` | required | Which page. Not needed on the named wrappers. |
| `title` / `description` | `ReactNode` | by kind | Replace the copy. |
| `code` | `string \| null` | 404, 500, 403, 503 by kind | The big number. `null` hides it. |
| `logo` | `ReactNode` | provider brand | Your logo, or `null`. |
| `errorId` | `string` | | 500: shown with a copy button. |
| `workspace` | `string` | | Unknown workspace: the address that failed. |
| `moduleName` | `string` | | Coming soon: what is coming. |
| `eta` | `number \| Date \| string` | | Maintenance: when it is back. |
| `online` | `boolean` | `false` | Offline: `true` turns it into "back online, reload". |
| `onHome` `onBack` `onRetry` `onContactSupport` `onSwitchWorkspace` `onRequestAccess` `onNotify` | `() => void \| Promise` | | Each shows its button only when passed. Async ones show a spinner while pending. |
| `actions` | `ReactNode` | by kind | Replace all buttons. |
| `fullScreen` | `boolean` | `true` | `min-h-dvh`. Turn off inside a panel. |
| `labels` | `Partial<ErrorPageLabels>` | | Override any string. |

**useOnlineStatus()** returns a boolean from `navigator.onLine` and the `online` / `offline` events. **statusCodeFor(kind)** returns the default code.

## Examples

**Offline that notices the connection coming back**

```tsx
import { OfflinePage, useOnlineStatus } from "@fadymondy/nasaq/web";

export function Offline() {
  const online = useOnlineStatus();
  return <OfflinePage online={online} onRetry={() => location.reload()} />;
}
```

## Accessibility

- The page is a `<main>` with one `<h1>`. The 500 page announces itself as an alert. Status is icon plus words, not colour alone.
- Async buttons show a spinner and cannot be pressed twice.

## RTL & i18n

- English and Arabic follow the locale. The status code and error ID stay left-to-right. The "back" arrow mirrors. The logo never mirrors.

## Styling & tokens

- Uses `bg-background`, `bg-card`, `text-display`, and the danger and warning text tokens. Target `[data-slot="error-page"]`.

## Do / Don't

- Do give a way forward on every page: home, retry, switch workspace.
- Do show an error ID on 500 so support can find the log.
- Don't show stack traces or internal messages.
- Don't reuse the 403 page to hide that something exists if that is sensitive: use not found instead.

## Related

- [`States`](https://docs.nasaqui.com/components/states)
- [`Alert`](https://docs.nasaqui.com/components/alert)

## Code

### React

```tsx
import { NotFoundPage, ServerErrorPage } from "@fadymondy/nasaq/web";

export const NotFound = () => <NotFoundPage onHome={() => router.push("/")} onBack={() => router.back()} />;

export const Crashed = ({ id }: { id: string }) => (
  <ServerErrorPage errorId={id} onRetry={() => router.refresh()} onContactSupport={() => openChat()} />
);
```

### shadcn

```tsx
import { NotFoundPage, ServerErrorPage } from "@/components/ui/error-pages";

export const NotFound = () => <NotFoundPage onHome={() => router.push("/")} onBack={() => router.back()} />;

export const Crashed = ({ id }: { id: string }) => (
  <ServerErrorPage errorId={id} onRetry={() => router.refresh()} onContactSupport={() => openChat()} />
);
```

### Vue

```vue
<script setup lang="ts">
import { NqNotFoundPage, NqServerErrorPage } from "@fadymondy/nasaq/vue";

const go = (path: string) => () => console.log("navigate", path);
</script>

<template>
  <div class="flex flex-col gap-8">
    <NqNotFoundPage :full-screen="false" :on-home="go('/')" :on-back="go('back')" />
    <NqServerErrorPage :full-screen="false" error-id="err_9f2a41" :on-retry="go('refresh')" :on-contact-support="go('chat')" />
  </div>
</template>
```

### Blade

```blade
<div class="flex flex-col gap-8">
    <x-nq::error-pages.not-found :full-screen="false" home-href="/" back />
    <x-nq::error-pages.server-error :full-screen="false" error-id="err_9f2a41" retry support-href="/support" />
</div>
```

### HTML + Alpine

```html
<div class="flex flex-col gap-8">
    <main data-slot="error-page" data-kind="not-found" x-data="nqErrorPage(false, false)"
    class="flex flex-col items-center justify-center gap-8 bg-background p-6 text-center text-foreground">
            <span data-slot="product-logo" class="inline-flex items-center gap-2">
    <svg data-slot="product-mark" viewBox="0 0 100 100" width="24" height="24"  aria-hidden="true"  shape-rendering="crispEdges"  class="shrink-0">
            <g class="dark:hidden">
                            <rect x="0" y="20" width="20" height="20" fill="#15694A" />
                            <rect x="80" y="20" width="20" height="20" fill="#15694A" />
                            <rect x="20" y="40" width="20" height="20" fill="#15694A" />
                            <rect x="60" y="40" width="20" height="20" fill="#15694A" />
                            <rect x="40" y="60" width="20" height="20" fill="#C9A227" />
                    </g>
        <g class="hidden dark:block">
                            <rect x="0" y="20" width="20" height="20" fill="#4CC495" />
                            <rect x="80" y="20" width="20" height="20" fill="#4CC495" />
                            <rect x="20" y="40" width="20" height="20" fill="#4CC495" />
                            <rect x="60" y="40" width="20" height="20" fill="#4CC495" />
                            <rect x="40" y="60" width="20" height="20" fill="#C9A227" />
                    </g>
    </svg>
            <span dir="ltr" class="font-mono font-medium tracking-[0.14em] uppercase text-foreground" style="font-size: 14.4px">NASAQ</span>
    </span>
        <div class="flex max-w-md flex-col items-center gap-4">
        <span aria-hidden="true"             class="inline-flex size-12 items-center justify-center rounded-card border border-border bg-card [&_svg]:size-6 text-muted-foreground">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    <path d="M12 17h.01"/>
    <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/>
    <path d="M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3"/>
</svg>                    </span>
                    <p dir="ltr" data-slot="error-page-code" class="text-display font-mono text-muted-foreground/60">404</p>
                <div class="flex flex-col gap-2" >
                            <h1 class="text-h2 text-foreground">We could not find that page</h1>
                                        <p class="text-body text-muted-foreground">The address may be mistyped, or the page may have moved or been removed.</p>
                    </div>
                            </div>
    <div class="flex flex-wrap items-center justify-center gap-2">
                                    <a data-slot="button"
     href="/"                         class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        Go to home</a>
                                    <button data-slot="button"
     type="button"                         x-on:click="back()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        <svg aria-hidden="true" class="rtl:-scale-x-100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m12 19-7-7 7-7"/>
  <path d="M19 12H5"/>
</svg>Go back</button>
                                        </div>
</main>
    <main data-slot="error-page" data-kind="server-error" x-data="nqErrorPage(false, false)"
    class="flex flex-col items-center justify-center gap-8 bg-background p-6 text-center text-foreground">
            <span data-slot="product-logo" class="inline-flex items-center gap-2">
    <svg data-slot="product-mark" viewBox="0 0 100 100" width="24" height="24"  aria-hidden="true"  shape-rendering="crispEdges"  class="shrink-0">
            <g class="dark:hidden">
                            <rect x="0" y="20" width="20" height="20" fill="#15694A" />
                            <rect x="80" y="20" width="20" height="20" fill="#15694A" />
                            <rect x="20" y="40" width="20" height="20" fill="#15694A" />
                            <rect x="60" y="40" width="20" height="20" fill="#15694A" />
                            <rect x="40" y="60" width="20" height="20" fill="#C9A227" />
                    </g>
        <g class="hidden dark:block">
                            <rect x="0" y="20" width="20" height="20" fill="#4CC495" />
                            <rect x="80" y="20" width="20" height="20" fill="#4CC495" />
                            <rect x="20" y="40" width="20" height="20" fill="#4CC495" />
                            <rect x="60" y="40" width="20" height="20" fill="#4CC495" />
                            <rect x="40" y="60" width="20" height="20" fill="#C9A227" />
                    </g>
    </svg>
            <span dir="ltr" class="font-mono font-medium tracking-[0.14em] uppercase text-foreground" style="font-size: 14.4px">NASAQ</span>
    </span>
        <div class="flex max-w-md flex-col items-center gap-4">
        <span aria-hidden="true"             class="inline-flex size-12 items-center justify-center rounded-card border border-border bg-card [&_svg]:size-6 text-nq-danger-text">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M6 10H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2"/>
  <path d="M6 14H4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2h-2"/>
  <path d="M6 6h.01"/>
  <path d="M6 18h.01"/>
  <path d="m13 6-4 6h6l-4 6"/>
</svg>                    </span>
                    <p dir="ltr" data-slot="error-page-code" class="text-display font-mono text-muted-foreground/60">500</p>
                <div class="flex flex-col gap-2"  role="alert" >
                            <h1 class="text-h2 text-foreground">Something went wrong on our side</h1>
                                        <p class="text-body text-muted-foreground">The error has been recorded. Try again in a moment, and contact support if it keeps happening.</p>
                    </div>
                            <p class="flex items-center gap-1 text-caption text-muted-foreground">
                Error ID
                <bdi dir="ltr" class="font-mono text-foreground">err_9f2a41</bdi>
                <span class="contents" x-data="nqCopyButton('err_9f2a41', 1500)">
    <button data-slot="copy-button"
     type="button"                         aria-label="Copy error ID" x-on:click="copy()" x-bind:data-copied="state === &#039;copied&#039; ? &#039;&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0 data-copied:text-nq-success-text">
        <svg aria-hidden="true" x-show="state !== 'copied'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
  <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg>        <svg aria-hidden="true" x-show="state === 'copied'" style="display: none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></button>
    <span data-slot="copy-button-status" role="status" aria-live="polite" class="sr-only"
        data-copied-label="Copied to clipboard" data-failed-label="Could not copy"
        x-text="state === 'copied' ? $el.dataset.copiedLabel : state === 'failed' ? $el.dataset.failedLabel : ''"></span>
</span>
            </p>
                    </div>
    <div class="flex flex-wrap items-center justify-center gap-2">
                                                        <button data-slot="button"
     type="button"                         x-bind="busyBtn(&#039;retry&#039;)" x-on:click="run(&#039;retry&#039;)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        <svg data-slot="spinner" aria-hidden="true" style="display: none;" x-show="busy === &#039;retry&#039;" class="size-4 animate-spin motion-reduce:animate-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
  <path d="M21 3v5h-5"/>
</svg>Try again</button>
                                                <a data-slot="button"
     href="/support"                         class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        Contact support</a>
                        </div>
</main>
</div>
```
