Aller au contenu

Badges

Status: Implemented Last reviewed: 2026-04-18 Sources of truth: static/css/base.css lines 644-650 (base) and 1020-1072 (semantic + entity variants); static/css/register.css lines 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

  1. Never use raw Bootstrap bg-* / text-bg-* on a badge. No bg-success, bg-warning, bg-primary, bg-secondary, etc. Always pick a semantic class from the catalogue below.
  2. Always combine badge + one semantic class. <span class="badge badge-status-active">badge alone gives shape; the semantic class gives meaning + colour.
  3. Badge text is translatable. Wrap the label in {% trans %} (templates) or _() (Python — usually via get_FOO_display() from a model choices).
  4. 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):

  1. Decide if it really needs its own family. A new colour for an existing family is rarely justified — try a status badge first.
  2. Add the CSS to static/css/base.css if 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.
  3. Use design tokens (var(--*)) only — do not write literal oklch(...) or #hex values even if existing entries do (they're known deviations).
  4. Add a row to the catalogue in this file.
  5. 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.
  6. Bump Last reviewed at the top.

Anti-patterns

  • <span class="badge bg-success"> — use badge-status-active.
  • <span class="badge text-bg-light"> — use badge-status-neutral.
  • <span class="badge badge-pill bg-warning text-dark"> — use badge-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 use get_FOO_display().
  • ❌ Inventing a 5th status (badge-status-warning, badge-status-info). Stretch the 4 existing ones or use badge-type with a modifier.
  • ❌ Using badge-type for a lifecycle state (or badge-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 — three bg-secondary badges. Replace with badge-status-neutral.
  • apps/finance/templates/finance/supplier_overview.html:138bg-warning text-dark. Replace with badge-status-pending.
  • apps/collection/templates/collection/agent_detail.html:163text-bg-light. Replace with badge-status-neutral.

Internal inconsistencies (not audit findings — improvement candidates):

  • Two naming schemes for entity-type variants. base.css uses compact keys (badge-entity-holding); register.css/captable.css use 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 in register.css and captable.css for entity and movement variants. Should resolve to var(--*) tokens — listed as a known issue in guidelines/ui/design-tokens.md and 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 the badge-* 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.