Exclusif

Error Page <mv-error-page>

Pages 404, 500 et 503 prêtes à l’emploi dans le registre éditorial : le code de statut composé comme un chiffre, un titre bicolore, une fiche technique réglée (ID de requête à copier, fenêtre de maintenance dans le fuseau horaire du visiteur), recherche du site ou inscription aux alertes, et des liens utiles ; mise en page scindée en option avec une planche illustrée.

CatégorieBlocs
TypeBloc (section prête à l’emploi) (<mv-error-page>)
Statutstable
Installe aussibutton, copy-button, input
Keywordsexclusive, error, 404, 500, 503, not found, server error, maintenance, status page, request id, landing, editorial

When to use

  • A site or app needs its 404 page with site search and links back to the main sections
  • A server error page must offer a retry, a status page link and a request ID that users can copy for support
  • A planned maintenance page should show when the service returns in the visitor's own time zone and offer an email alert
  • An error page should sit beside a sober illustration or background motif instead of a plain column

Avoid when

  • A list, table or search inside a working page simply has no results → use Empty instead
  • One request failed but the rest of the page still works, so a banner or inline message is enough → use Alert instead
  • The section opens a marketing page rather than replacing a page that failed → use Hero instead

Installation

node scripts/add.mjs error-page --out ./src/marvelous

Agent IA avec le serveur MCP de Marvelous UI : install_components({ slugs: ["error-page"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Fichiers copiés (dépendances comprises) : tokens/tokens.css, core/base.css, components/button/button.css, core/element.js, core/motion.js, components/copy-button/copy-button.js, components/copy-button/copy-button.css, components/input/input.css, components/error-page/error-page.css, components/error-page/error-page.js.

Utilisation

Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les variables CSS :

<mv-tabs id="ep-demo" value="not-found" data-size="sm" style="gap:0">
  <style>
    #ep-demo > [role="tablist"] { justify-self: center; margin: .75rem auto; }
    #ep-demo .mv-error-page { --mv-error-page-min-height: 0; }
  </style>

  <div role="tablist" aria-label="Error page variant">
    <button role="tab" data-value="not-found">Not found</button>
    <button role="tab" data-value="server-error">Server error</button>
    <button role="tab" data-value="maintenance">Maintenance</button>
    <button role="tab" data-value="split">Split</button>
  </div>

  <!-- ═════════ 404: not found (default) ═════════ -->
  <div role="tabpanel" data-value="not-found">
    <mv-error-page>
      <section class="mv-error-page" data-variant="not-found" aria-labelledby="ep-nf-title">
        <div class="mv-error-page-inner">
          <p class="mv-error-page-meta">
            <span class="mv-error-page-kicker">Error 404</span>
            <span>Page not found</span>
            <span class="mv-error-page-path">/pricing/enterprize</span>
          </p>
          <p class="mv-error-page-code" aria-hidden="true">404</p>
          <div class="mv-error-page-content">
            <h1 class="mv-error-page-title" id="ep-nf-title">We can’t find that page. <span class="mv-error-page-muted">It may have moved.</span></h1>
            <p class="mv-error-page-lead">The link may be out of date, or the address has a typo. Search the site, or pick up again from one of the pages below.</p>
            <form class="mv-error-page-form" role="search" action="#ep-demo" data-demo-search>
              <label class="mv-sr-only" for="ep-nf-q">Search Ledgerline</label>
              <div class="mv-input-group" data-size="lg">
                <label class="mv-input-group-addon" for="ep-nf-q"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg></label>
                <input class="mv-input" id="ep-nf-q" name="q" type="search" placeholder="Search guides, pricing and docs" autocomplete="off">
                <button class="mv-button" data-size="sm" type="submit">Search</button>
              </div>
            </form>
            <p class="mv-error-page-note">Did you mean <a href="#ep-demo"><span class="mv-error-page-path">/pricing/enterprise</span></a>?</p>
          </div>
          <nav class="mv-error-page-links" aria-label="Helpful pages">
            <ul>
              <li><a href="#ep-demo"><span class="mv-error-page-link-label">Home</span><span class="mv-error-page-link-text">Product tour, customers and plans</span></a></li>
              <li><a href="#ep-demo"><span class="mv-error-page-link-label">Documentation</span><span class="mv-error-page-link-text">Guides, API reference and changelog</span></a></li>
              <li><a href="#ep-demo"><span class="mv-error-page-link-label">Help center</span><span class="mv-error-page-link-text">Answers from our support team, 24/7</span></a></li>
              <li><a href="#ep-demo"><span class="mv-error-page-link-label">System status</span><span class="mv-error-page-link-text">Live uptime for every region</span></a></li>
            </ul>
          </nav>
        </div>
      </section>
    </mv-error-page>
  </div>

  <!-- ═════════ 500: server error ═════════ -->
  <div role="tabpanel" data-value="server-error">
    <mv-error-page id="ep-se">
      <section class="mv-error-page" data-variant="server-error" aria-labelledby="ep-se-title">
        <div class="mv-error-page-inner">
          <p class="mv-error-page-meta">
            <span class="mv-error-page-kicker">Error 500</span>
            <span>Internal server error</span>
            <a href="#ep-demo">status.ledgerline.com →</a>
          </p>
          <p class="mv-error-page-code" aria-hidden="true">500</p>
          <div class="mv-error-page-content">
            <h1 class="mv-error-page-title" id="ep-se-title">Something broke on our side. <span class="mv-error-page-muted">Your data is safe.</span></h1>
            <p class="mv-error-page-lead">The server hit an unexpected error while loading your invoices. Our on-call engineers have been alerted, and trying again usually works within a minute.</p>
            <div class="mv-error-page-actions">
              <a class="mv-button" href="" data-retry>
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 4v5h-5"/></svg>
                <span data-retry-label>Try again</span>
              </a>
              <a class="mv-button" data-variant="outline" href="#ep-demo">View system status</a>
            </div>
            <p class="mv-error-page-status" id="ep-se-status" role="status" aria-live="polite"></p>
            <p class="mv-error-page-note">Still stuck? <a href="#ep-demo">Contact support</a> and include the request ID below.</p>
          </div>
          <dl class="mv-error-page-facts">
            <div><dt>Status</dt><dd>500 Internal Server Error</dd></div>
            <div><dt>Request ID</dt><dd><code id="ep-se-ref">req_01J8X4K2Q7M9WZ</code><mv-copy-button target="#ep-se-ref" data-variant="ghost" label="Copy request ID" success-label="Request ID copied"></mv-copy-button></dd></div>
            <div><dt>Time</dt><dd><time datetime="2026-09-25T14:32:08Z">Sep 25, 2026, 2:32 PM UTC</time></dd></div>
          </dl>
        </div>
      </section>
    </mv-error-page>
  </div>

  <!-- ═════════ 503: maintenance ═════════ -->
  <div role="tabpanel" data-value="maintenance">
    <mv-error-page id="ep-mt">
      <section class="mv-error-page" data-variant="maintenance" aria-labelledby="ep-mt-title">
        <div class="mv-error-page-inner">
          <p class="mv-error-page-meta">
            <span class="mv-error-page-kicker">Scheduled maintenance</span>
            <span>Error 503</span>
            <a href="#ep-demo">status.ledgerline.com →</a>
          </p>
          <p class="mv-error-page-code" aria-hidden="true">503</p>
          <div class="mv-error-page-content">
            <h1 class="mv-error-page-title" id="ep-mt-title">We’re down for planned maintenance. <span class="mv-error-page-muted">Back shortly.</span></h1>
            <p class="mv-error-page-lead">We’re moving every workspace to faster storage. <strong>Nothing is lost</strong>, and bank feeds catch up on their own once we’re back.</p>
            <form class="mv-error-page-form" id="ep-mt-form" action="#ep-demo" novalidate>
              <label for="ep-mt-email">Get an email when we’re back</label>
              <div class="mv-input-group" data-size="lg">
                <label class="mv-input-group-addon" for="ep-mt-email"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></svg></label>
                <input class="mv-input" id="ep-mt-email" name="email" type="email" placeholder="[email protected]" autocomplete="email" aria-describedby="ep-mt-status">
                <button class="mv-button" data-size="sm" type="submit">Notify me</button>
              </div>
              <p class="mv-error-page-status" id="ep-mt-status" role="status" aria-live="polite"></p>
            </form>
            <p class="mv-error-page-note">Live progress on the <a href="#ep-demo">status page</a>. One email, then we delete your address.</p>
          </div>
          <dl class="mv-error-page-facts">
            <div><dt>Started</dt><dd><time id="ep-mt-start" datetime="2026-09-25T13:00:00Z">Sep 25, 1:00 PM UTC</time></dd></div>
            <div><dt>Back by</dt><dd><time id="ep-mt-end" datetime="2026-09-25T16:30:00Z" data-relative>Sep 25, 4:30 PM UTC</time></dd></div>
            <div><dt>Affected</dt><dd>Web app, API and mobile sync</dd></div>
          </dl>
        </div>
      </section>
    </mv-error-page>
  </div>

  <!-- ═════════ Split layout (combinable with every variant) ═════════ -->
  <div role="tabpanel" data-value="split">
    <mv-error-page>
      <section class="mv-error-page" data-variant="not-found" data-layout="split" aria-labelledby="ep-sp-title" style="--mv-error-page-bg:var(--mv-bg-subtle)">
        <div class="mv-error-page-inner">
          <div class="mv-error-page-main">
            <p class="mv-error-page-meta">
              <span class="mv-error-page-kicker">Error 404</span>
              <span>Careers</span>
            </p>
            <div class="mv-error-page-content">
              <h1 class="mv-error-page-title" id="ep-sp-title">This role has been filled. <span class="mv-error-page-muted">Fourteen others are open.</span></h1>
              <p class="mv-error-page-lead">The posting for <strong>Senior Product Designer, Lisbon</strong> closed on August 14. Similar roles are listed below, and new ones open every month.</p>
              <div class="mv-error-page-actions">
                <a class="mv-button" href="#ep-demo">See open roles</a>
                <a class="mv-button" data-variant="ghost" href="#ep-demo">Get job alerts →</a>
              </div>
            </div>
            <nav class="mv-error-page-links" aria-label="Similar roles">
              <ul>
                <li><a href="#ep-demo"><span class="mv-error-page-link-label">Product Designer</span><span class="mv-error-page-link-text">Berlin or remote in Europe</span></a></li>
                <li><a href="#ep-demo"><span class="mv-error-page-link-label">Design Systems Lead</span><span class="mv-error-page-link-text">Lisbon, hybrid</span></a></li>
              </ul>
            </nav>
          </div>
          <figure class="mv-error-page-visual" aria-hidden="true">
            <div class="mv-error-page-plate">
              <svg viewBox="0 0 400 300" preserveAspectRatio="xMaxYMin meet" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
                <path d="M40 150 C 90 150, 100 92, 150 92 S 220 132, 262 118 S 318 64, 340 58" stroke-dasharray="3 6"/>
                <rect x="36" y="146" width="8" height="8" fill="currentColor" stroke="none"/>
                <rect x="146" y="88" width="8" height="8" fill="currentColor" stroke="none"/>
                <rect x="258" y="114" width="8" height="8" fill="currentColor" stroke="none"/>
                <g style="color:var(--mv-error-page-accent)">
                  <rect x="328" y="46" width="24" height="24" stroke-dasharray="4 4"/>
                  <path d="M336 54l8 8M344 54l-8 8"/>
                </g>
              </svg>
              <p class="mv-error-page-code">4<span>0</span>4</p>
            </div>
            <figcaption class="mv-error-page-caption"><span><b>Fig. 404</b> · /careers/senior-product-designer-lisbon</span><span>Closed Aug 14</span></figcaption>
          </figure>
        </div>
      </section>
    </mv-error-page>
  </div>

  <script type="module">
    const root = document.querySelector("#ep-demo");

    // Search: stays on the page in this demo (a real form goes to /search).
    for (const form of root.querySelectorAll("[data-demo-search]")) form.addEventListener("submit", (e) => e.preventDefault());

    // Retry: cancel the reload here and report the attempt instead.
    const seStatus = root.querySelector("#ep-se-status");
    let seTimer;
    root.querySelector("#ep-se")?.addEventListener("mv-retry", (e) => {
      e.preventDefault();
      const control = e.detail.control;
      control.setAttribute("aria-busy", "true");
      control.setAttribute("aria-disabled", "true");
      clearTimeout(seTimer);
      seTimer = setTimeout(() => {
        control.removeAttribute("aria-busy");
        control.removeAttribute("aria-disabled");
        const at = new Intl.DateTimeFormat("en-US", { hour: "numeric", minute: "2-digit", second: "2-digit" }).format(new Date());
        seStatus.dataset.state = "error";
        seStatus.textContent = `Still unavailable at ${at}. We’ll keep the page ready for your next try.`;
      }, 900);
    });

    // Maintenance window around the current time, so the relative note reads well.
    const step = 15 * 60_000, now = Date.now();
    const start = new Date(Math.floor((now - 50 * 60_000) / step) * step);
    const end = new Date(Math.ceil((now + 2 * 3600_000) / step) * step);
    const fallback = (d) => new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", timeZone: "UTC", timeZoneName: "short" }).format(d);
    for (const [id, d] of [["#ep-mt-start", start], ["#ep-mt-end", end]]) {
      const time = root.querySelector(id);
      if (time) { time.dateTime = d.toISOString(); time.textContent = fallback(d); }
    }
    customElements.whenDefined("mv-error-page").then(() => root.querySelector("#ep-mt")?.refresh?.());

    // Alert signup: validate and confirm in the live region.
    const form = root.querySelector("#ep-mt-form");
    const status = root.querySelector("#ep-mt-status");
    const input = form?.querySelector("input");
    form?.addEventListener("submit", (e) => {
      e.preventDefault();
      const email = input.value.trim();
      if (!email || !input.checkValidity()) {
        status.dataset.state = "error";
        status.textContent = "Enter an email address like [email protected].";
        input.setAttribute("aria-invalid", "true");
        return;
      }
      input.removeAttribute("aria-invalid");
      input.value = "";
      status.dataset.state = "success";
      status.textContent = `Done. We’ll write to ${email} as soon as we’re back.`;
    });
  </script>
</mv-tabs>

API

Attributes

NameTypeDefaultDescription
data-variantnot-found | server-error | maintenancenot-foundOn .mv-error-page: the kind of error. Sets the color of the kicker mark (accent, danger, warning); the content pattern of each kind is shown in the demo.
data-layoutsplitOn .mv-error-page: text in .mv-error-page-main on one side, figure.mv-error-page-visual on the other (from 56rem). Works with every variant.
data-motifgrid | dots | nonegridOn .mv-error-page-plate: background motif, faded toward the code.
data-retrybooleanOn a control (an <a href=""> reloads even without JS): with <mv-error-page>, fires mv-retry, then reloads the page. Put the label in a [data-retry-label] child to have it replaced while reloading.
data-relativebooleanOn a <time datetime>: <mv-error-page> adds a relative note after it (“in 2 hours”), refreshed every minute.
data-formatauto | time | dateautoOn a <time datetime>: auto shows the time alone when it is today, else weekday, date and time, always with the viewer's time zone.
localestringOn <mv-error-page>: locale for dates and relative times. Default: the nearest lang attribute, then en-US.

Properties

NameTypeDescription
stringsobjectOverride the default text: retrying (“Reloading…”), soon (“any minute now”), overdue (“taking longer than planned”). Can be set before the element is defined.

Methods

NameDescription
refresh()Re-format every <time datetime> and relative note (call it after changing a datetime).

Events

NameDescription
mv-retryCancelable, fired by a [data-retry] control before the reload; detail = { control }. Call preventDefault() to retry in place (refetch in a single-page app).

Content structure

NameDescription
.mv-error-page-innerDefault layout: -meta, -code, -content, then optionally -facts and -links. From 48rem the code and facts sit left of the content.
.mv-error-page-contentCopy: -title (with -muted), -lead, then -actions, -form, -status and -note as needed.
form.mv-error-page-formSite search (role="search") or an alert signup: a label (visible or .mv-sr-only), an .mv-input-group with its button, and an optional -status.
dl.mv-error-page-factsRuled spec sheet: div > dt (mono label) + dd. Holds the status line, a request ID with <mv-copy-button>, or the maintenance window in <time> elements.
nav.mv-error-page-linksHelpful links: ul > li > a > -link-label + -link-text. 1, 2, then up to 4 columns.
figure.mv-error-page-visualSplit layout: .mv-error-page-plate (motif, optional <svg> or <img> behind, the -code) + figcaption.mv-error-page-caption.

CSS classes

NameDescription
mv-error-pageRoot section, a query container named mv-error-page.
mv-error-page-meta / -kickerMono uppercase meta line on a hairline; the kicker gets a small colored square. A trailing item (path, status link) is pushed to the end.
mv-error-page-codeThe status code set like a figure. Decorative: add aria-hidden="true", the kicker and title carry the meaning. A <span> inside greys one digit.
mv-error-page-pathMono path or identifier inside running text or the meta line; long values wrap.
mv-error-page-statusForm or retry feedback (role="status"), hidden when empty; data-state="success | error" colors it.
mv-error-page-mainSplit layout: wraps the text side (meta, content, facts, links).
mv-error-page-relativeRelative note added after a <time data-relative>.

CSS variables

NameDefaultDescription
--mv-error-page-max72remMax content width.
--mv-error-page-min-height100svhMinimum height; content is centered vertically. Set 0 inside a layout with a header.
--mv-error-page-bgvar(--mv-bg)Section background.
--mv-error-page-rulevar(--mv-border-strong)Color of the main hairlines (meta, facts, links).
--mv-error-page-accentvar(--mv-accent)Kicker mark of the not-found variant, and the accent of a plate illustration.

Accessibility

One <h1> per page: the title states the problem in words, the kicker names the error, and the big code is aria-hidden, so the meaning never rests on the number alone. Set document.title to match (“Page not found · Ledgerline”) and return the real HTTP status from the server (404, 500, 503 with Retry-After) so crawlers and assistive tech are not told the page worked. The search form has role="search" and a label; the links sit in a labeled <nav>; form and retry feedback go to a polite role="status" region, errors spelled out in text. The retry control gets aria-busy and aria-disabled while it reloads. Times are real <time datetime> elements, rewritten in the viewer's time zone with a full-date tooltip. Keyboard: native links, inputs and buttons only. The plate and its illustration are aria-hidden. Reduced motion: no entrance. Forced colors: marks and hairlines use system colors, the motif is dropped.

Cette page a été traduite par IA. Signaler un problème de traduction