Skip to main content

Component Model

The Component Model is the Component Semantics Projectionlayer 3 of the FSL architecture. It derives from the FSL Lexicon and FSL Structural Language and must not define vocabulary that contradicts them.

The central rule:

A component has an immutable identity. An instance carries that identity into a composition.

FSL dimension mapping

The model adopts FSL dimension names directly (no projection renames; FSL §17.1 permits renames but this profile keeps the foundation vocabulary):

FSL dimensionModel nameNotes
Entity KindEntityValues identical; field name is entity in ComponentMeta and all *Meta declarations
Structural RoleStructureRoot structural role of the component (e.g. root); legal values constrained per Entity via ENTITY_STRUCTURE
Composition RoleCompositionFlat vocabulary; values identical to FSL Lexicon §4. Per-Entity legality via ENTITY_COMPOSITION in taxonomy.ts.
Interaction KindDeferred per FSL §13.3 — not codified in this profile. See taxonomy.ts §Dimension Coverage for rationale and readmission criterion.
EvaluationEvaluationValues identical
ConsequenceConsequenceValues identical
StateStateValues identical; runtime-resolved by React Aria render props, not authorially declared

Entity → Token UX context mapping

The normative Entity → ux context mapping lives in ENTITY_TOKEN_MAPPING in packages/ui2/src/tokens/projection.ts — that constant is the single source of truth consumed by the resolver and enforced by contract tests. The table below is a read-only mirror for authoring reference; when the two disagree, projection.ts wins.

EntityToken ux contextNotes
Actionaction1:1
Inputinput1:1
SelectioninputSelection components consume input.* tokens; no separate selection UX context
Navigationnavigation1:1
Feedbackfeedback1:1
CollectioninformationalCollection surfaces consume informational.* for structural coloring
OverlayinformationalOverlay surfaces consume informational.* for surface coloring
DisclosurenavigationDisclosure triggers colored as navigation.* when acting as location anchors
StructureinformationalStructural surfaces consume informational.*

For the full ux role and state grammar, see the Colors family — FSL Entity Kind Mapping.

ComponentExpression

The model is expressed as a ComponentExpression — the typed semantic expression that the resolver consumes:

type ComponentExpression = {
entity: Entity; // required — what the component IS
composition?: CompositionRole; // optional — flat slot name (FSL Lexicon §4)
evaluation?: Evaluation; // optional — emphatic meaning
consequence?: Consequence; // optional — risk profile
};

All dimensions are defined in taxonomy.ts and derived from FSL core vocabulary.

Code type: The implementation exposes ComponentMeta<E> from semantics/ — the identity type every component declares (entity, structure, composition?, consequence?). ComponentExpression above is the projection's conceptual shape; ComponentMeta is its current runtime surface.


Entity

Entity answers: What is this component?

Every component has exactly one Entity. It cannot change based on context, variant, or usage. If a different Entity is required, a different component must exist.

EntityUse forTypical examples
Actiontriggering actions or commandsbutton, action button, icon button
Inputdirect user inputtext field, text area, search field
Selectionchoosing one or more optionscheckbox, radio group, select, picker
Collectionstructured sets of itemsmenu, list, table, tree, grid
Navigationmovement across destinations or viewslink, breadcrumbs, tabs, nav item
Disclosurerevealing or hiding related content in placeaccordion, disclosure trigger
Overlaytemporary layered UI above the interfacedialog, popover, tooltip, drawer
Feedbackcommunicating state, status, or outcomealert, banner, toast, progress
Structureorganizing interface structure and support surfacespanel, section, shell, frame

Composition Model

Composition answers: What slot does this instance occupy inside a larger composite?

Composition is a flat vocabulary per FSL Lexicon §4 and FSL §5.4. A composition role names the slot; legality is per Entity (not per parent component). When omitted, the component resolves tokens from its Entity default.

Composition roles

The projection codifies 14 composition roles. The table shows each role's meaning and which Entities may carry it (source of truth: ENTITY_COMPOSITION in taxonomy.ts).

RoleMeaningLegal Entities
primaryActionmain forward / commit action in a compositeAction
secondaryActionsubordinate but intentional actionAction
dismissActioncancel / close without committingAction
headingcompositional heading slotOverlay, Structure
bodycompositional body slotOverlay, Structure
statuscompositional status / validation slotInput, Feedback
controlprimary control-bearing slotInput, Selection, Disclosure
labelnaming / label slotInput, Selection, Structure
descriptiondescriptive / helper-text slotInput, Selection, Structure
supportingsupporting child slot (broader than label/description)Input, Structure
selectionselection-bearing slotSelection
stepstep slot (e.g. progression marker)Structure
summarysummary slotStructure
navigationnavigation slot inside a structural compositeStructure

Parent disambiguation

Because the vocabulary is flat, the same role name may appear in multiple composites — for example, a label slot exists on both TextField (Input) and a Structure composite. Runtime and CSS disambiguation comes from the rendered DOM, not from adding a host level to the data model:

  • data-scope + data-part on the composite container identify the parent (e.g. data-scope="dialog" data-part="actions" on DialogActions).
  • data-composition on the slot-bearing child carries the role name.

Example selector: [data-scope="dialog"][data-part="actions"] [data-composition="primaryAction"] resolves a dialog's primary action unambiguously, without a host level.


Evaluation

Evaluation answers: What emphatic or evaluative meaning does this expression carry?

Evaluation is optional. When omitted, it is inferred by the resolver from the Entity and composition context. Add it explicitly only when the default inference is wrong.

ValueUse for
primarymain intended emphasis
secondarysubordinate but still intentional
accentdeliberately differentiated emphasis
mutedde-emphasized but still meaningful
positiveaffirming, successful, or favorable
cautionwarning or careful-attention signal
negativeharmful, erroneous, or adverse

Consequence

Consequence answers: What user-facing consequence or risk profile does this carry?

Consequence is optional. When omitted, neutral is implied. Distinct from Evaluation: negative is evaluative meaning; destructive is outcome risk — both may appear simultaneously.

ValueUse for
neutralno special risk profile
reversibleeffect can be undone
committingmoves to a more committed state
destructivecauses deletion or materially harmful loss
interruptiveinterrupts flow and demands handling
recoverableadverse path exists but recovery is supported
safeDefaultRequiredthe safer path must be privileged

Interaction

Interaction Kind is a FSL foundational dimension (FSL Lexicon §3, FSL §5.3) that this profile does not currently codify. The disposition is Deferred per FSL §13.3.

Readmission requires a component that dispatches behaviour on Interaction Kind at runtime — for example, a Wizard that progresses on navigate.step versus a Link that follows navigate.link. Until such a prototype exists, the dimension carries no expressible distinction in ComponentMeta. See taxonomy.ts §Dimension Coverage for the full rationale.


State

State answers: What interactional or semantic condition is currently active?

State is not a prop passed at the expression level — it is runtime-resolved by React Aria render props (isHovered, isFocused, isPressed, isSelected, …) and surfaced as the CSS selector layer:

StateMeaning
defaultNo special condition active
hoverPointer is over the element
activeElement is being activated / pressed
focusedElement has keyboard focus
disabledElement is not interactive
selectedItem is selected within a collection
pressedToggle is in its on-press state
checkedCheckbox-like element is checked
indeterminateMixed or partial selection state
expandedDisclosure or select is open
currentNavigation item matches the current location
visitedLink has been previously visited
droptargetElement is a valid target for a drag operation

Not all states are meaningful for every Entity — checked is only surfaced by selection components; visited only by navigation links. Legality here is React Aria's runtime concern (it only emits the render-prop for applicable primitives), not a build-time matrix.


How to use the model

1. Set Entity

Classify the component itself.

  • ButtonAction
  • SearchFieldInput
  • MenuCollection
  • DialogOverlay

2. Add Composition when the instance occupies a slot in a composite

  • Button in a dialog footer → composition: 'primaryAction'
  • Button as a form submit → composition: 'primaryAction'
  • TextField validation message → composition: 'status'
  • Checkbox inside a RadioGroup-like set → composition: 'selection'

Legal values depend on the component's Entity — see the Composition roles table above.

3. Add Evaluation when the default inference isn't right

Most expressions don't need explicit Evaluation. Add it when the standard inference is wrong:

  • Destructive confirm button → evaluation: 'negative'
  • Success state feedback → evaluation: 'positive'
  • Subdued ghost action → evaluation: 'muted'

4. Add Consequence when the interaction carries a material risk profile

  • Delete / irreversible action → consequence: 'destructive'
  • Save with no undo → consequence: 'committing'

Usage Examples

Token paths reference the Semantic Token Projection (layer 4). They reflect the current token projection and will be confirmed during its re-engineering.

// Save
{ entity: 'Action', composition: 'primaryAction' }
// → action.primary.background.default, action.primary.text.default

// Back to editing
{ entity: 'Action', composition: 'secondaryAction' }
// → action.secondary.background.default, action.secondary.text.default

// Cancel
{ entity: 'Action', composition: 'dismissAction' }
// → action.muted.text.default

TextField (Input composite)

// Main control
{ entity: 'Input', composition: 'control' }
// → input.primary.background.default, input.primary.border.default, input.primary.text.default

// Label
{ entity: 'Input', composition: 'label' }
// → input.primary.text.default

// Helper text
{ entity: 'Input', composition: 'description' }
// → input.muted.text.default

// Validation message
{ entity: 'Input', composition: 'status' }
// → input.negative.text.default

Dialog (Overlay composite)

// Heading
{ entity: 'Overlay', composition: 'heading' }
// → informational.primary.text.default

// Body
{ entity: 'Overlay', composition: 'body' }
// → informational.primary.text.default

Form (Structure composite)

// Actions row container
{ entity: 'Structure', composition: 'supporting' }
// → informational.muted.background.default

// Submit button inside the actions row
{ entity: 'Action', composition: 'primaryAction' }
// → action.primary.background.default

Destructive action

{
entity: 'Action',
composition: 'primaryAction',
evaluation: 'negative',
consequence: 'destructive',
}
// → action.negative.background.default, action.negative.text.default
// A `destructive` consequence authorises downstream safe-default treatment of the cancel path.

Rules

  1. Entity is always primary. It defines the component, not the instance.
  2. Composition names a slot, not the component. Composition never replaces Entity.
  3. Composition legality is per Entity. A role is only valid on the Entities listed in the Composition roles table (source: ENTITY_COMPOSITION).
  4. Evaluation is semantic, not visual. Choose it for its meaning, not its color.
  5. Consequence is about outcome risk. It shapes interaction policy before styling.
  6. Keep the model small. New values must come from the FSL foundation or be added through FSL governance.

Implementation: See UI Components for how this model is realized in React.