Level: organism.
Purpose and use
Host a focused task in a modal dialog over the current screen. Use Popup for a nonmodal content surface. The parent controls whether this dialog remains open.
Anatomy, behavior, and accessibility
- Optional header/title/description and close button surround a scrollable default
content slot;
[wblModalFooter]supplies footer actions. XL alone uses customWidth. - Close-button, Escape and eligible backdrop activation emit closeRequest and openedChange(false); the parent must update opened. closable only hides the header close button and does not disable Escape/backdrop closure.
- Backdrop closure is blocked if descendant input/textarea controls contain values (or checked checkbox/radio inputs). This is a DOM-value heuristic, not dirty-form detection; Escape and the close button bypass it.
- The dialog sets aria-modal, traps/autocaptures focus, locks page scrolling and restores previous focus when closed. Overflow changes divider presentation.
- Use a meaningful title or ariaLabel; ariaLabel overrides title naming. With no header, supply ariaLabel. Footer buttons and their actions belong to the caller.
Open Questions
Concurrent/nested modals share body overflow without a stack manager. The backdrop heuristic does not inspect select controls, custom controls or saved-versus-dirty values; do not treat it as a complete unsaved-change safeguard.
Local component contract
This is the active owned contract for component.modal. The API below
is verified against the current local implementation, focused test and story.
The Source commit 0112cba0779a2d2a4b6c7a17243201694344d70a remains
migration provenance, not the runtime owner.
Classes and selectors
WblModalComponent—wbl-modal(src/app/design-system/patterns/modal/modal.component.ts).
Inputs
WblModalComponent.opened = input(false, { transform: booleanAttribute })WblModalComponent.size = input<WblModalSize>('S')WblModalComponent.customWidth = input('82.5rem')WblModalComponent.header = input(true, { transform: booleanAttribute })WblModalComponent.description = input(false, { transform: booleanAttribute })WblModalComponent.closable = input(true, { transform: booleanAttribute })WblModalComponent.footer = input(true, { transform: booleanAttribute })WblModalComponent.title = input('Title')WblModalComponent.descriptionText = input('Subtitle')WblModalComponent.ariaLabel = input<string | null>(null)
Public types and allowed values
type WblModalSize = 'S' | 'M' | 'L' | 'XL' | 'Full';
Models
- None.
Outputs
WblModalComponent.openedChange = output<boolean>()WblModalComponent.closeRequest = output<void>()
Slots and projection markers
WblModalComponent: <ng-content>WblModalComponent: <ng-content select="[wblModalFooter]">
Local evidence
- Code:
src/app/design-system/patterns/modal/modal.component.ts. - Focused test:
src/app/design-system/patterns/modal/modal.component.spec.ts. - Public export:
export * from './patterns/modal/modal.component';insrc/app/design-system/index.ts. - Storybook:
storybook/stories/modal.stories.ts(Design System/Patterns/wbl-modal).
Figma status
This runtime contract does not infer a Figma owner. Assembly evidence, when verified, is maintained separately through the inventory-linked sidecar.
Provenance
The archived source-document copy is historical evidence only. The active resolver is local code/test/export and the exact Storybook story above.
M-002 refinement
The Transport Requests delete confirmation keeps title as the dialog name and
moves its explanatory copy into a normal <p> projected inside
.wbl-modal__content. The integration does not use description or
descriptionText; the footer remains projected through [wblModalFooter].
This current screen composition adds no Modal input, output, or slot and is
verified against Figma nodes 17034:12382 and 17034:12383 in file
uGBDiLA947AJwkuvpohikQ.
M-002 delete body typography
The projected explanatory <p> in the Transport Requests delete dialog uses
the regular --typography-body-horse role and does not inherit bold or accent
weight from the title or footer. This current screen composition adds no Modal
API and is covered by the feature's focused test.