Tucano v0.37.2

Alert

What stays true while the person is on the screen: "your subscription expires in 3 days", "this contract is locked for editing". The toast is for what just happened and goes away on its own; an alert that disappears before it's read didn't alert anyone. Classes only, no JavaScript.

The figures on this screen are refreshed every 5 minutes.

Contract signed

Both parties signed on 2026-09-12.

Subscription expires in 3 days

Renew so you don't lose access to reports.

Contract locked for editing

There's a disputed charge. Contact the billing team.

Examples

The parts are optional: title, icon and actions go in only when they're needed.

No tone

Without is-* the alert is neutral, for what's neither good nor bad.

Draft saved at 14:32.

No icon

The icon is the first child, when there is one; without it the text sits against the edge.

Scheduled maintenance

Saturday, 10 PM to 11 PM.

With two actions

The actions are the system's buttons. On a narrow screen, they drop below the text.

There are unpublished changes.

Shows up later

The response to a submit gets role="alert" and is announced right away.

How to use

A .tuc-alert with the tone, the icon as the first child and the text in .tuc-alert__body.

<div class="tuc-alert is-warning">
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01M10.3 3.9L1.8 18a2 2 0 001.7 3h17a2 2 0 001.7-3L13.7 3.9a2 2 0 00-3.4 0z"/></svg>
  <div class="tuc-alert__body">
    <p class="tuc-alert__title">Subscription expires in 3 days</p>
    <p>Renew so you don't lose access.</p>
  </div>
  <div class="tuc-alert__actions">
    <a class="tuc-btn is-outline is-sm" href="/subscription/">Renew</a>
  </div>
</div>

The icon doesn't come from the library: it's the SVG you paste, and it inherits the tone's color. The ones on this page, to copy:

<!-- info -->
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 16v-4M12 8h.01M12 22a10 10 0 100-20 10 10 0 000 20z"/></svg>
<!-- success -->
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 11.1V12a10 10 0 11-5.9-9.1M22 4L12 14l-3-3"/></svg>
<!-- warning -->
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01M10.3 3.9L1.8 18a2 2 0 001.7 3h17a2 2 0 001.7-3L13.7 3.9a2 2 0 00-3.4 0z"/></svg>
<!-- danger -->
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 22a10 10 0 100-20 10 10 0 000 20zM15 9l-6 6M9 9l6 6"/></svg>

In a Django template

The error that doesn't belong to a field, non_field_errors, is the most common case. And Django messages can become an alert, instead of a toast, when they need to stay on the screen. Django's error level is called error; the alert's, is-danger.

{% if form.non_field_errors %}
  <div class="tuc-alert is-danger">
    <div class="tuc-alert__body">
      <p class="tuc-alert__title">Couldn't save</p>
      {% for error in form.non_field_errors %}<p>{{ error }}</p>{% endfor %}
    </div>
  </div>
{% endif %}

{% for m in messages %}
  <div class="tuc-alert {% if m.level_tag == 'error' %}is-danger{% else %}is-{{ m.level_tag }}{% endif %}">
    <div class="tuc-alert__body"><p>{{ m }}</p></div>
  </div>
{% endfor %}

The debug level becomes is-debug, which has no rule, so the alert stays neutral.

Accessibility

role="alert" only on an alert that appears after the page loads: the response to a submit, an HTMX swap.

Why not on all of them

An alert that's already in the HTML is read along with the page. With role="alert" it would interrupt the screen reader for nothing, and on a page with three alerts, three times. What arrives later needs the role precisely to interrupt: without it, the submit response shows up on screen and no screen reader user ever finds out.

{# partial HTMX response: shows up later, so it interrupts #}
<div class="tuc-alert is-success" role="alert">
  <div class="tuc-alert__body"><p>Proposal sent to {{ customer.email }}.</p></div>
</div>

The icon gets aria-hidden="true": the text is what says what happened, and the tone can't be the only cue.

Color

The background is a light tint of the tone over --tuc-bg, not the transparent -soft from the tokens: over a colored card or an image, the transparent one would leave the text without guaranteed contrast.

The tones read --tuc-info, --tuc-success, --tuc-warning and --tuc-danger, and never --tuc-accent. The brand can be any color, and the meaning can't change along with it. That's why info got its own tone: with an orange accent, the info alert looked the same as the warning one.

Classes

Class or attributeWhat it's for
.tuc-alertThe alert. Without a tone, neutral
is-info is-success is-warning is-dangerThe tone
> svgIcon, as the first child: 16px, centered on the first line, in the tone's color
.tuc-alert__bodyThe text; takes up the remaining space
.tuc-alert__titleFirst line in bold. The rest of the body uses the muted color
.tuc-alert__actionsButtons on the right; below 40rem, they drop below the text
role="alert"Only on what appears after the page loads
--tuc-alert-toneThe color the tone picks; background, border and icon come from it