System Pages
The full-page states every app needs but nobody designs first —
errors, maintenance windows, empty lists, loading screens and
first-run onboarding. One calm scaffold (.b-syspage),
one outcome panel (.b-result) and an onboarding stack,
all composed from components you already know.
Error Page Anatomy
Every error page is the same four pieces stacked on
.b-syspage, a flex column that centers itself in the
viewport: .b-syspage__code — the oversized status
number wearing the accent-to-indigo gradient —
.b-syspage__title for the human headline,
.b-syspage__desc for one or two sentences on what
actually happened, and .b-syspage__actions, a button
row with exactly one primary way out. Swap the code for an icon and
the same scaffold covers maintenance and any other full-page
interruption.
404 Page
The classic. Keep the description honest — most 404s are stale
links, not user error — and route people home or to a human. The
demo pins min-height: 0; the real page keeps the
default 60vh so it centers in the viewport.
The page you're looking for was moved, renamed, or never existed. Check the address, or head back somewhere familiar.
<title> ("Page not found
— Barua"), make .b-syspage__title the page's only
<h1>, and render the scaffold inside a
<main> landmark so screen-reader users land
straight on the explanation. Return the real HTTP status too — the
semantics live in the response, not the visuals.403 Page
Denied, not broken. Offer the two exits people actually want: request access, or hop identities. Pair "Switch account" with the Account Switcher from Authentication so nobody has to sign out just to sign back in.
Your account doesn't have permission to view this workspace. Ask an admin to let you in, or try a different account.
500 Page
Own it. Say the fault is on your side, promise the data is safe,
and hand over a reference code support can actually use — a plain
<code> line tucked under the actions.
An unexpected error stopped this request. It's not you — your mail is safe, and the team has already been paged.
Reference: ERR-2AF4-9C01
Maintenance Page
The same scaffold minus the status code — an icon where the number
would be, a concrete time window, and an indeterminate
.b-progress so the page feels alive rather than
abandoned.
Barua is getting a planned upgrade between 02:00 and 04:30 UTC. Mail keeps arriving the whole time and will be waiting when we're back.
Started 02:00 UTC · Live updates on the status page
Offline State
A .b-result--warning panel, not a dead end — reassure
people their drafts are safe locally. Toggling it (listening to
navigator.onLine and the online /
offline events) is app-side wiring; Barua ships the
look.
Barua can't reach the network. Drafts are saved on this device and will send themselves the moment you're back online.
Permission State
Ask in context, right before the capability is used. A
.b-result--confirm panel primes the real browser
prompt — explain the payoff first and the OS dialog stops feeling
like an ambush.
Get a ping the moment important mail lands — VIP senders and mentions only, never the newsletter flood.
Loading State
Two tools, both from Feedback: a
centered spinner when you don't know the shape of what's coming,
skeletons when you do. Full-page loading is a
.b-spinner--lg plus one quiet line of
.b-footnote text — never a paragraph.
Loading your inbox…
Skeleton page
When the layout is known, ghost it instead: compose
.b-skeleton variants inside the real containers so
nothing jumps when content arrives.
aria-busy="true" while it loads and remove it when
content arrives. Announce the transition through a polite live
region — role="status" or
aria-live="polite" — with a short label like "Loading
your inbox". Skeletons are decorative; hide them from assistive tech
with aria-hidden="true" rather than labelling each
shimmering bar.Empty State
The component lives in Content — this is
the pattern. On first run, render .b-empty inside the
exact container the content will eventually fill, with the creation
action right there instead of hidden in a toolbar.
Rules file, label and archive mail before it ever hits your inbox. Start with one for newsletters — future you says thanks.
Onboarding
The pitch page. .b-onboarding__hero gives one word the
gradient treatment via <em>, a single primary CTA
leads, and at most three .b-onboarding-feature rows
carry the argument — icons from .b-icon-tile.
One inbox for every account, triaged before you even open it. No folders to invent, no zero to chase.
Welcome Screen
The Apple-style first-launch sheet: app icon up top
(.b-auth-card__logo), a "Welcome to" hero, a short
feature list, and a single Continue button in a
.b-form-actions footer. In a real sheet, pin that
footer to the bottom edge so Continue never scrolls away.