Badges¶
Status: Implemented Last reviewed: 2026-04-18 Sources of truth:
static/css/base.csslines 644-650 (base) and 1020-1072 (semantic + entity variants);static/css/register.csslines 44-107 (entity + movement variants for the register module). Examples:apps/entities/templates/entities/entity_detail.html,templates/skeletons/skeleton_detail.html.
Scope¶
Inline status / type indicators rendered as <span class="badge ...">. Covers: which class to use for which semantic meaning, the family-modifier pattern (entity types, movement types), and the rule against raw Bootstrap utility colors.
Out of scope: alerts (.alert-*), action buttons (.btn-action-*), table-row accents (data-movement-group left borders in register.css).
Hard rules¶
- Never use raw Bootstrap
bg-*/text-bg-*on a badge. Nobg-success,bg-warning,bg-primary,bg-secondary, etc. Always pick a semantic class from the catalogue below. - Always combine
badge+ one semantic class.<span class="badge badge-status-active">—badgealone gives shape; the semantic class gives meaning + colour. - Badge text is translatable. Wrap the label in
{% trans %}(templates) or_()(Python — usually viaget_FOO_display()from a modelchoices). - Badges carry meaning, not decoration. If a badge isn't communicating state, type, or category — it shouldn't be a badge. Use a chip, tag, or plain text.
The catalogue¶
Status badges (4 states)¶
For row-level / record-level lifecycle state. Pick exactly one per badge.
| Class | Meaning | Color | Use for |
|---|---|---|---|
badge-status-active |
"this is on / live / current" | green tint (--success-100/700) |
Active, Oui, Enabled, Open, Confirmed |
badge-status-pending |
"in transition / awaiting action" | amber tint (--warning-100/700) |
Pending, En attente, Draft, Provisoire |
badge-status-inactive |
"off / closed / archived" | red tint (--error-100/700) |
Inactive, Non, Closed, Archived, Disabled |
badge-status-neutral |
"informational, no state judgement" | grey (--bg-tertiary/--text-secondary) |
Country tags, version numbers, period labels, anything that isn't a lifecycle |
Don't invent a 5th status. If you find yourself wanting "warning" + "pending" + "info" as separate states, you're conflating lifecycle with priority. Use status for lifecycle; use a separate column / chip for priority.
Type / category badges¶
For "what kind of thing is this." Always use badge-type as the base; optionally add a family modifier alongside.
<span class="badge badge-type">{% trans "Catégorie" %}</span>
<span class="badge badge-type badge-entity-holding">{{ entity.get_entity_type_display }}</span>
Default badge-type = brand tint (--bg-interactive / --brand-700). Family modifiers override the colour while keeping the shape.
Family modifier — entity types¶
Defined in static/css/base.css (compact set) and static/css/register.css (extended set). Worn alongside badge-type.
Compact set (general use, in base.css):
| Class | Tint |
|---|---|
badge-entity-holding |
brand-100 / brand-800 |
badge-entity-spfpl |
info-100 / info-800 |
badge-entity-operating |
success-100 / success-800 |
badge-entity-service |
neutral-200 / neutral-700 |
badge-entity-other |
neutral-100 / neutral-600 |
Extended set (cap-table specifics, in register.css and captable.css):
badge-entity-top_holding, badge-entity-intermediate_holding, badge-entity-central_holding, badge-entity-spfpl_holding, badge-entity-operating_practice, badge-entity-service_company.
The extended set uses full snake-cased names matching the model's entity_type choices; the compact set uses short keys. They're not aliases — pick the one that matches the value coming from your data.
The model exposes a helper for picking the right modifier: entity.entity_type_badge_class returns the CSS class string. Render as <span class="badge badge-type {{ entity.entity_type_badge_class }}">.
Family modifier — movement types¶
Defined in static/css/register.css lines 78-107. Worn alongside badge-type. Used on the share-register movement list.
badge-movement-creation · badge-movement-transfer · badge-movement-modification · badge-movement-conversion · badge-movement-transformation · badge-movement-other.
Each has a colour-coded pair plus a left-border accent on the parent <tr> (via data-movement-group="...") for faster visual scanning of the register table.
Common patterns¶
Boolean field on a list / detail page¶
{% if obj.is_active %}
<span class="badge badge-status-active">{% trans "Oui" %}</span>
{% else %}
<span class="badge badge-status-inactive">{% trans "Non" %}</span>
{% endif %}
Lifecycle status with multiple states (4-way)¶
{% if entity.lifecycle_status == 'active' %}
<span class="badge badge-status-active">{{ entity.get_lifecycle_status_display }}</span>
{% elif entity.lifecycle_status == 'in_creation' %}
<span class="badge badge-status-pending">{{ entity.get_lifecycle_status_display }}</span>
{% elif entity.lifecycle_status == 'closed' %}
<span class="badge badge-status-inactive">{{ entity.get_lifecycle_status_display }}</span>
{% else %}
<span class="badge badge-status-neutral">{{ entity.get_lifecycle_status_display }}</span>
{% endif %}
Type with family modifier¶
<span class="badge badge-type {{ entity.entity_type_badge_class }}">
{{ entity.get_entity_type_display }}
</span>
Status badge inside a detail-page heading¶
Wrap the status badge in <div class="mt-2"> immediately under the title — see templates/skeletons/skeleton_detail.html and guidelines/ui/detail-pages.md (when written).
Adding a new family modifier¶
If you need a new badge family (e.g. for a new domain like contracts, payments):
- Decide if it really needs its own family. A new colour for an existing family is rarely justified — try a status badge first.
- Add the CSS to
static/css/base.cssif it's a shared family (used by 2+ apps). Add to a per-module CSS file (e.g.register.css) if it's truly module-local. - Use design tokens (
var(--*)) only — do not write literaloklch(...)or#hexvalues even if existing entries do (they're known deviations). - Add a row to the catalogue in this file.
- If the variant is data-driven, expose it via a model property (mirror
entity_type_badge_class) so templates don't carry the mapping logic. - Bump
Last reviewedat the top.
Anti-patterns¶
- ❌
<span class="badge bg-success">— usebadge-status-active. - ❌
<span class="badge text-bg-light">— usebadge-status-neutral. - ❌
<span class="badge badge-pill bg-warning text-dark">— usebadge-status-pending. - ❌ Raw colours via
style="background: #..."— same hard rule as design-tokens. - ❌ Hardcoded English / French strings inside
<span class="badge">— wrap in{% trans %}or useget_FOO_display(). - ❌ Inventing a 5th status (
badge-status-warning,badge-status-info). Stretch the 4 existing ones or usebadge-typewith a modifier. - ❌ Using
badge-typefor a lifecycle state (orbadge-status-*for a category). The family carries semantics — don't cross-wire them.
Known deviations¶
Surfaced by the 2026-04 audit (see roadmap/backlog/ui-design-system-mechanical-fixes-2026-04.md):
apps/annuaire/templates/annuaire/changes.html:125,130,135— threebg-secondarybadges. Replace withbadge-status-neutral.apps/finance/templates/finance/supplier_overview.html:138—bg-warning text-dark. Replace withbadge-status-pending.apps/collection/templates/collection/agent_detail.html:163—text-bg-light. Replace withbadge-status-neutral.
Internal inconsistencies (not audit findings — improvement candidates):
- Two naming schemes for entity-type variants.
base.cssuses compact keys (badge-entity-holding);register.css/captable.cssuse snake-cased model values (badge-entity-top_holding). They're not aliases. Picking the right one currently requires knowing where the variant is consumed. Candidate for a challenge pass: pick one scheme and converge. - Hardcoded
oklch(...)colors inregister.cssandcaptable.cssfor entity and movement variants. Should resolve tovar(--*)tokens — listed as a known issue inguidelines/ui/design-tokens.mdand tracked in the same audit backlog item.
When to break the rules¶
- Charts and data visualisations (e.g. ApexCharts, kanban headers) often need bespoke colour badges that don't fit the 4 statuses or the type families. Use chart palette tokens (
--chart-color-1, …,--chart-color-6) not raw hex, but skip thebadge-*semantic classes. - Third-party widget integration (e.g. an embedded Doctolib widget) may emit its own badges. Don't try to override; isolate in a wrapper.