78 UI Kit
Components

Notifications

Four components, one rule — and the rule is the point. Pick by what the user has to do, not by how it looks.

Which one

ComponentUse forBehaviorAPI
Modal Needs acknowledgement — errors, confirms Blocks. Requires a click. _78.modal
Toast Informational — “Saved”, “Copied” Auto-dismisses. Never blocks. _78.notify
Inline alert Tied to a region — form errors, empty states Sits in the layout. Persists. _78-alert (CSS)
Quiet notes Worth knowing, nothing to do — “close to target”, why a button isn't there Sits in a card. Never louder than the content. _78-notes (CSS)

Toast

Stacks in the corner, auto-dismisses, never blocks. Default lifetime is 4 seconds; errors get 6. These buttons are real:

live toast triggers
<button class="_78-btn" type="button" id="save">Save</button>
_78.notify
_78.notify('Plain informational toast');
_78.notify('Workspace saved', { type: 'success' });

// Shorthands for the four types
_78.notify.success('Workspace saved');
_78.notify.error('Could not reach the server');   // .danger() is the same, by its tone name
_78.notify.warn('You are near your seat limit');
_78.notify.info('A new version is available');
OptionTypeWhat it does
typeany tone, or 'error'Sets the accent stripe and the title color. 'danger' and the toast's own 'error' are the same thing: longer on screen and announced at once (role="alert"). Omit for the neutral toast.
titlestringA bold line above the message.
durationnumber (ms)Default 4000, or 6000 for errors. 0 makes it sticky — it then only leaves by its close button or its handle.
htmlbooleanTreat the message as markup. Off by default.

The message may also be a DOM node, which is appended as-is — no html: true needed, and no escaping question.

The return handle

notify() returns { el, close }. That is how you dismiss a sticky toast when the thing it was reporting on finishes.

the toast handle
var t = _78.notify('Uploading…', { duration: 0 });
// t.el    → the toast element, if you need to update it
// t.close() → dismiss it now, clearing any pending timer

upload().then(function () {
  t.close();
  _78.notify.success('Upload complete');
});

Where the stack sits

Bottom-right by default. Two classes on the _78-toasts container move it — set them once, on the container the kit creates.

toast placement
// The stack is created on first use; grab it and position it.
document.querySelector('._78-toasts').classList.add('_78-toasts-top');   // top instead of bottom
document.querySelector('._78-toasts').classList.add('_78-toasts-left');  // left instead of right

Inline alert

Pure CSS apart from dismissal. The icon and title are optional; the content belongs in _78-alert-body — the alert is a flex row, so bare text and an inline link dropped straight into it become separate flex items and the sentence breaks apart.

Use role="alert" for problems, which is announced immediately, and role="status" for everything else, which waits for a pause.

Usage is reported daily
Numbers on this page can lag by up to 24 hours.
Import complete
142 records added.
Accent variant — a neutral highlight tied to a region.
Plain variant — no semantic color.
inline alerts
<div class="site-demo-block">
  <div class="_78-alert _78-alert-info" role="status">
    <svg class="_78-alert-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 11v5M12 8h.01"/></svg>
    <div class="_78-alert-body">
      <div class="_78-alert-title">Usage is reported daily</div>
      Numbers on this page can lag by up to 24 hours.
    </div>
  </div>

  <div class="_78-alert _78-alert-success" role="status">
    <div class="_78-alert-body"><div class="_78-alert-title">Import complete</div>142 records added.</div>
    <button class="_78-alert-close" type="button" aria-label="Dismiss">&times;</button>
  </div>

  <div class="_78-alert _78-alert-warn" role="alert">
    <div class="_78-alert-body"><div class="_78-alert-title">Approaching your seat limit</div>210 of 220 seats are in use.</div>
  </div>

  <div class="_78-alert _78-alert-danger" role="alert">
    <div class="_78-alert-body"><div class="_78-alert-title">Payment failed</div>Update the card on file to avoid interruption.</div>
  </div>

  <div class="_78-alert _78-alert-accent">
    <div class="_78-alert-body">Accent variant &mdash; a neutral highlight tied to a region.</div>
  </div>

  <div class="_78-alert">
    <div class="_78-alert-body">Plain variant &mdash; no semantic color.</div>
  </div>
</div>

_78-alert-close is the one piece of behavior: the kit removes the alert when it is clicked. No wiring needed.

Lists in an alert

A plain <ul> or <ol> inside _78-alert-body keeps its indent and markers, in every tone. The same holds inside a card, a modal body and a disclosure. For a list that should not show bullets, use _78-list-plain.

Before you import
  1. Export the sheet as CSV
  2. Keep the header row
lists in an alert
<div class="site-demo-block">
  <div class="_78-alert _78-alert-danger" role="alert">
    <div class="_78-alert-body">
      <div class="_78-alert-title">Three rows were rejected</div>
      <ul>
        <li>Row 7: unknown plan name</li>
        <li>Row 12: seat count is zero</li>
        <li>Row 31: duplicate account ID</li>
      </ul>
    </div>
  </div>

  <div class="_78-alert _78-alert-info" role="status">
    <div class="_78-alert-body">
      <div class="_78-alert-title">Before you import</div>
      <ol>
        <li>Export the sheet as CSV</li>
        <li>Keep the header row</li>
      </ol>
    </div>
  </div>
</div>
Fixed in 0.3.0
The reset strips list padding, which used to leave an alert's bullets hanging outside its box. Lists with no class of their own now get an indent inside the components that hold prose. If you added your own padding as a workaround, you can delete it.

Paragraphs in an alert

Two <p>s in a row inside _78-alert-body are spaced --space-2 apart. Inside a card or a disclosure body the gap is --space-3, the same as a modal body. The rule carries no weight of its own, so a margin utility on the paragraph — or any rule of yours — still wins.

Worth a look

Earnings are due on Thursday, before the open.

This account has been idle for 18 days, past its review date.

Nothing has been changed. These are things to know, not instructions.

paragraphs in an alert
<div class="_78-alert _78-alert-warn" role="status">
  <div class="_78-alert-body">
    <div class="_78-alert-title">Worth a look</div>
    <p>Earnings are due on Thursday, before the open.</p>
    <p>This account has been idle for 18 days, past its review date.</p>
    <p class="_78-text-dim _78-text-sm">Nothing has been changed. These are things to know, not instructions.</p>
  </div>
</div>
Changed in 0.4.0

Consecutive paragraphs used to run together, because the reset zeroes their margins. If you spaced them with your own class, it still works — and you can now delete it.

Cards and disclosure bodies with several paragraphs pick up a --space-3 gap they did not have before.

Empty state

The other half of “tied to a region” — a region with nothing in it yet.

No projects yet
Anything you create will show up here.
empty state
<div class="_78-empty">
  <div class="_78-empty-title">No projects yet</div>
  <div class="_78-text-sm">Anything you create will show up here.</div>
  <button class="_78-btn _78-btn-primary _78-btn-sm _78-mt-8" type="button">New project</button>
</div>

Quiet notes

Not everything worth saying is worth an alert. "Close to target, nothing to do yet", the renewal date under an account, the reason an action isn't offered — put those in an alert and the card shouts about nothing; put them in _78-empty and they read as missing content. _78-notes is the quiet one: dim, small, and after other content in a card it sits where a card foot would.

Renewals

All twelve accounts are on track this quarter.

Due soon, nothing to do yet:
  • Northwind renews in 12 days, on autopay
  • Contoso renews in 19 days, on autopay
_78-notes _78-notes-ruled
<div class="_78-card">
  <div class="_78-card-title">Renewals</div>
  <p>All twelve accounts are on track this quarter.</p>
  <div class="_78-notes _78-notes-ruled">
    <div class="_78-notes-title">Due soon, nothing to do yet:</div>
    <ul>
      <li>Northwind renews in 12 days, on autopay</li>
      <li>Contoso renews in 19 days, on autopay</li>
    </ul>
  </div>
</div>

A line's tone

Each line gets a small dot marker in --dim. Any _78-tone-* on a line colors its marker, and the text stays quiet — so one block can hold an earnings date, a time limit and a market change without every line sharing one alarm color. A tone on the block colors every marker; a tone on a line overrides it.

_78-notes-box is the compact alert: the same notes in a quiet inset with a 3px edge in the block's tone. It's what to reach for when two stacked alerts in one card read heavy.

Accounts

Three need a look before Friday.

Worth a look
  • Fabrikam's card expires at the end of the month
  • Contoso is over its seat limit by two
  • Northwind asked about the annual plan

Nothing has been changed. These are things to know, not instructions.

_78-notes-box with line tones
<div class="_78-card">
  <div class="_78-card-title">Accounts</div>
  <p>Three need a look before Friday.</p>
  <div class="_78-notes _78-notes-box _78-tone-warn">
    <div class="_78-notes-title">Worth a look</div>
    <ul>
      <li>Fabrikam's card expires at the end of the month</li>
      <li>Contoso is over its seat limit by two</li>
      <li class="_78-tone-info">Northwind asked about the annual plan</li>
    </ul>
    <p class="_78-notes-xs">Nothing has been changed. These are things to know, not instructions.</p>
  </div>
</div>

Nothing to do, and why

One sentence is a note too. When the action a reader expects isn't there, say why in the space it would have taken:

Upgrade

Team plan, 14 seats.

No upgrade offered: this workspace is already on the largest plan its billing region supports.

a one-line note in a card foot
<div class="_78-card">
  <div class="_78-card-title">Upgrade</div>
  <p>Team plan, 14 seats.</p>
  <div class="_78-card-foot">
    <p class="_78-notes">No upgrade offered: this workspace is already on the largest plan its billing region supports.</p>
  </div>
</div>
ClassWhat it does
_78-notesThe block, or a single <p>: --dim, --fs-sm. After other content in a card, --space-4 above it.
_78-notes-titleAn optional small heading: --text, semibold, sentence case.
<ul> / <ol>Each line gets a 6px dot marker; lines sit --space-1 apart.
_78-tone-* on a lineColors that line's marker — any tone or category.
_78-tone-* on the blockColors every marker and the box edge.
_78-notes-boxThe compact alert: a --bg3 inset with a 3px toned left edge.
_78-notes-ruledA hairline above — a footnote under the card's content.
_78-notes-xs--fs-xs, on the block or on one paragraph in it.

The marker is reinforcement, never the message: write each line so it reads without its color.

Written in HTML

No JavaScript built this one — it is plain markup in the page, opened with _78.modal.show(el).

The close button is a <form method="dialog">, so it would close even with JavaScript disabled.