Traits / Statusable
Statusable
Semantic status presentation layer that maps business statuses to visual tones, design tokens, and badge/banner rendering. Statusable owns the status registry pattern: a domain-scoped lookup table that translates raw status strings into consistent visual output (tone, label, icon, token sets). DISTINCT from Stateful — Stateful governs state machine transitions and guards; Statusable governs how status LOOKS. Statusable is the bridge between data-layer state and the component layer (Badge, Banner, StatusBadge). Core concepts: - Status Registry: Map<domain, Map<status, StatusPresentation>> - Tone System: Six semantic tones (neutral, info, accent, success, warning, critical) - Token Resolution: --cmp-status-{tone}-{surface|border|text} CSS variables - Domain Scoping: Each domain (subscription, invoice, ticket, etc.) has its own status map - Emphasis Variants: badge.subtle / badge.solid, banner.subtle / banner.solid
Generated from @oods/foundry 0.10.1
- Group
- visual
- Maturity
- experimental
- Contexts
- detail, form, list, timeline
Fields it adds
| Field | Type | Required | Description |
|---|---|---|---|
status | string | yes | Raw business status string (e.g., "active", "past_due", "unpaid"). Resolved against the domain's status map to derive presentation data. |
domain | StatusDomain | yes | Domain scope for registry lookup. Determines which status map is consulted (e.g., "subscription" statuses differ from "invoice" statuses). |
tone | StatusTone (neutral, info, accent, success, warning, critical) | no | Semantic tone derived from the registry or inferred from token paths. One of: neutral, info, accent, success, warning, critical. Falls back to the defaultTone parameter when unresolvable. |
badge_tokens | StatusTokenVariants | no | Resolved badge token sets with subtle and solid emphasis variants. Each variant contains { background, border, foreground } CSS variable references. Structure: { subtle: StatusTokenSet, solid: StatusTokenSet } |
banner_tokens | StatusTokenVariants | no | Resolved banner token sets with subtle and solid emphasis variants. Subtle banners use domain-specific tokens when available; solid banners use the tone-based component token set. Structure: { subtle: StatusTokenSet, solid: StatusTokenSet } |
What it shows in each context
Parameters
domainsstring[], required- Domain identifiers whose statuses this object participates in. Each domain resolves to a scoped status map in the registry (e.g., "subscription", "invoice", "ticket", "user").
toneAliasesRecord<string, StatusTone>- Custom alias map for inferring tones from token path segments. Merged with built-in aliases (info, positive→success, danger→critical, etc.). Keys are token path segments; values are canonical StatusTone values.
defaultToneStatusTone- Fallback tone applied when a status is not found in the registry or when tone inference from token paths yields no match.
Objects that use it
No shipped object uses this trait.
The trait file
traits/visual/Statusable.trait.yaml
trait:
name: Statusable
version: 1.0.0
description: |
Semantic status presentation layer that maps business statuses to visual tones,
design tokens, and badge/banner rendering. Statusable owns the status registry
pattern: a domain-scoped lookup table that translates raw status strings into
consistent visual output (tone, label, icon, token sets).
DISTINCT from Stateful — Stateful governs state machine transitions and guards;
Statusable governs how status LOOKS. Statusable is the bridge between data-layer
state and the component layer (Badge, Banner, StatusBadge).
Core concepts:
- Status Registry: Map<domain, Map<status, StatusPresentation>>
- Tone System: Six semantic tones (neutral, info, accent, success, warning, critical)
- Token Resolution: --cmp-status-{tone}-{surface|border|text} CSS variables
- Domain Scoping: Each domain (subscription, invoice, ticket, etc.) has its own status map
- Emphasis Variants: badge.subtle / badge.solid, banner.subtle / banner.solid
category: visual
tags:
- status
- presentation
- registry
- badge
- banner
- tone
- semantic
- saas
parameters:
- name: domains
type: string[]
required: true
description: |
Domain identifiers whose statuses this object participates in.
Each domain resolves to a scoped status map in the registry
(e.g., "subscription", "invoice", "ticket", "user").
default:
- subscription
- name: toneAliases
type: Record<string, StatusTone>
required: false
description: |
Custom alias map for inferring tones from token path segments.
Merged with built-in aliases (info, positive→success, danger→critical, etc.).
Keys are token path segments; values are canonical StatusTone values.
default: {}
- name: defaultTone
type: StatusTone
required: false
description: |
Fallback tone applied when a status is not found in the registry
or when tone inference from token paths yields no match.
default: neutral
validation:
enum:
- neutral
- info
- accent
- success
- warning
- critical
schema:
status:
type: string
required: true
description: |
Raw business status string (e.g., "active", "past_due", "unpaid").
Resolved against the domain's status map to derive presentation data.
domain:
type: StatusDomain
required: true
description: |
Domain scope for registry lookup. Determines which status map is consulted
(e.g., "subscription" statuses differ from "invoice" statuses).
validation:
enumFromParameter: domains
tone:
type: StatusTone
required: false
description: |
Semantic tone derived from the registry or inferred from token paths.
One of: neutral, info, accent, success, warning, critical.
Falls back to the defaultTone parameter when unresolvable.
validation:
enum:
- neutral
- info
- accent
- success
- warning
- critical
badge_tokens:
type: StatusTokenVariants
required: false
description: |
Resolved badge token sets with subtle and solid emphasis variants.
Each variant contains { background, border, foreground } CSS variable references.
Structure: { subtle: StatusTokenSet, solid: StatusTokenSet }
banner_tokens:
type: StatusTokenVariants
required: false
description: |
Resolved banner token sets with subtle and solid emphasis variants.
Subtle banners use domain-specific tokens when available; solid banners
use the tone-based component token set.
Structure: { subtle: StatusTokenSet, solid: StatusTokenSet }
semantics:
status:
semantic_type: status.presentation.status
token_mapping: tokenMap(status.registry.*)
ui_hints:
component: StatusBadge
registryLookup: getStatusPresentation(domain, status)
domain:
semantic_type: status.presentation.domain
token_mapping: tokenMap(status.domains.*)
ui_hints:
component: DomainSelector
scopedTo: domains
tone:
semantic_type: status.presentation.tone
token_mapping: tokenMap(status.tone.*)
ui_hints:
component: ToneBadge
toneValues:
- neutral
- info
- accent
- success
- warning
- critical
badge_tokens:
semantic_type: status.presentation.badge
token_mapping: tokenMap(cmp.status.{tone}.*)
ui_hints:
component: Badge
emphasisVariants:
- subtle
- solid
banner_tokens:
semantic_type: status.presentation.banner
token_mapping: tokenMap(cmp.status.{tone}.*)
ui_hints:
component: Banner
emphasisVariants:
- subtle
- solid
view_extensions:
# StatusBadge rather than Badge, for the reason lifecycle/Supersedable already records: the
# canonical Badge contract carries no field directive, so a Badge authored with these props is
# refused by the target contracts. Measured in s204-m02 against code.generate rather than assumed —
# Badge reds on THREE of the four props below (statusField, domainField and showIcon) in both React
# and Vue, while StatusBadge takes all four with zero errors. No object composes Statusable's list
# extension today, so this sat unexercised as a latent refusal rather than a visible break; every
# other context in this trait already authors StatusBadge.
list:
- component: StatusBadge
position: after
props:
statusField: status
domainField: domain
emphasis: subtle
showIcon: true
detail:
- component: StatusBadge
position: main
priority: 80
props:
statusField: status
domainField: domain
emphasis: subtle
showIcon: true
- component: Banner
position: top
priority: 90
props:
statusField: status
domainField: domain
emphasis: subtle
showIcon: true
conditions:
tones:
- warning
- critical
form:
- component: StatusBadge
position: top
props:
statusField: status
domainField: domain
emphasis: subtle
readOnly: true
timeline:
- component: StatusBadge
props:
statusField: status
domainField: domain
emphasis: subtle
showIcon: true
compact: true
tokens:
# Badge tone tokens — pattern: --cmp-status-{tone}-{surface|border|text}
cmp.status.neutral.surface: "var(--sys-surface-neutral)"
cmp.status.neutral.border: "var(--sys-border-neutral)"
cmp.status.neutral.text: "var(--sys-text-neutral)"
cmp.status.info.surface: "var(--sys-surface-info)"
cmp.status.info.border: "var(--sys-border-info)"
cmp.status.info.text: "var(--sys-text-info)"
cmp.status.accent.surface: "var(--sys-surface-accent)"
cmp.status.accent.border: "var(--sys-border-accent)"
cmp.status.accent.text: "var(--sys-text-accent)"
cmp.status.success.surface: "var(--sys-surface-success)"
cmp.status.success.border: "var(--sys-border-success)"
cmp.status.success.text: "var(--sys-text-success)"
cmp.status.warning.surface: "var(--sys-surface-warning)"
cmp.status.warning.border: "var(--sys-border-warning)"
cmp.status.warning.text: "var(--sys-text-warning)"
cmp.status.critical.surface: "var(--sys-surface-critical)"
cmp.status.critical.border: "var(--sys-border-critical)"
cmp.status.critical.text: "var(--sys-text-critical)"
# Banner fallback tokens
cmp.banner.background: "var(--sys-surface-raised)"
cmp.banner.border: "var(--sys-border-default)"
cmp.banner.text: "var(--sys-text-default)"
# Statusable can work standalone with static status strings.
# When composed with Stateful, status values are driven by the state machine.
# Stateful is optional — not listed as a dep to avoid required-dep enforcement.
dependencies: []
metadata:
created: "2026-02-28"
owners:
- design@oods.systems
- engineering@oods.systems
maturity: experimental
accessibility:
keyboard: Badge and Banner elements are not interactive; no keyboard handling required.
screenreader: |
StatusBadge announces status label and tone as a live region.
Banner uses role="status" with aria-live="polite" for critical/warning tones.
regionsUsed:
- list
- detail
- form
- timeline
examples:
- Subscription
- Invoice
- Ticket
- User
references:
- "objects/provenance.v1.json#/references/ref-ca663a7c5731 — StatusPresentation, StatusTone, tone inference, domain maps"
- "objects/provenance.v1.json#/references/ref-24923e661958 — 7 sub-stories across 5 domains"
- "objects/provenance.v1.json#/references/ref-be33a98177f7 — domain status token map consumed by registry"
- "objects/provenance.v1.json#/references/ref-c373015a9045 — StatusableViewData, StatusTransitionEvent interfaces"
- "objects/provenance.v1.json#/references/ref-cc4cadcf82d5 — formatStatus, resolveDisplayName utilities"