Páginas 404, 500 y 503 listas para usar en el registro editorial: el código de estado compuesto como una cifra, un título de dos tonos, una ficha técnica con reglas (ID de solicitud para copiar, ventana de mantenimiento en la zona horaria de quien visita), búsqueda del sitio o registro de alertas, y enlaces útiles; diseño dividido opcional con una lámina ilustrada.
Categoría
Bloques
Tipo
Bloque (sección lista para usar) (<mv-error-page>)
Agente de IA con el servidor MCP de Marvelous UI: install_components({ slugs: ["error-page"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Marcado de referencia: parte de él y personalízalo con atributos, data-* y 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
Name
Tipo
Default
Description
data-variant
not-found | server-error | maintenance
not-found
On .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-layout
split
On .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-motif
grid | dots | none
grid
On .mv-error-page-plate: background motif, faded toward the code.
data-retry
boolean
On 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-relative
boolean
On a <time datetime>: <mv-error-page> adds a relative note after it (“in 2 hours”), refreshed every minute.
data-format
auto | time | date
auto
On a <time datetime>: auto shows the time alone when it is today, else weekday, date and time, always with the viewer's time zone.
locale
string
On <mv-error-page>: locale for dates and relative times. Default: the nearest lang attribute, then en-US.
Properties
Name
Tipo
Description
strings
object
Override the default text: retrying (“Reloading…”), soon (“any minute now”), overdue (“taking longer than planned”). Can be set before the element is defined.
Methods
Name
Description
refresh()
Re-format every <time datetime> and relative note (call it after changing a datetime).
Events
Name
Description
mv-retry
Cancelable, 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
Name
Description
.mv-error-page-inner
Default layout: -meta, -code, -content, then optionally -facts and -links. From 48rem the code and facts sit left of the content.
.mv-error-page-content
Copy: -title (with -muted), -lead, then -actions, -form, -status and -note as needed.
form.mv-error-page-form
Site 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-facts
Ruled 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-links
Helpful links: ul > li > a > -link-label + -link-text. 1, 2, then up to 4 columns.
figure.mv-error-page-visual
Split layout: .mv-error-page-plate (motif, optional <svg> or <img> behind, the -code) + figcaption.mv-error-page-caption.
CSS classes
Name
Description
mv-error-page
Root section, a query container named mv-error-page.
mv-error-page-meta / -kicker
Mono 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-code
The 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-path
Mono path or identifier inside running text or the meta line; long values wrap.
mv-error-page-status
Form or retry feedback (role="status"), hidden when empty; data-state="success | error" colors it.
mv-error-page-main
Split layout: wraps the text side (meta, content, facts, links).
mv-error-page-relative
Relative note added after a <time data-relative>.
CSS variables
Name
Default
Description
--mv-error-page-max
72rem
Max content width.
--mv-error-page-min-height
100svh
Minimum height; content is centered vertically. Set 0 inside a layout with a header.
--mv-error-page-bg
var(--mv-bg)
Section background.
--mv-error-page-rule
var(--mv-border-strong)
Color of the main hairlines (meta, facts, links).
--mv-error-page-accent
var(--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.