Pular para o conteúdo

Scrim component

components specs/components/scrim.kmd

A dimming veil that fills its bounds, dims whatever is stacked beneath an overlay, and makes it inert — swallowing every pointer event so nothing behind can be clicked or hovered. A press (or Escape) dismisses it unless it is a blocking backdrop. It is the reusable primitive that a Dialog's built-in scrim is a specialised case of; gov.br ships `scrim` as a first-class component and KDS had it only implicit in dialogs — this makes it standalone.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

Scrim

R1 — The veil

A scrim fills its full bounds with a single translucent-black wash and paints nothing else. It is the backdrop an overlay (bottom-sheet, drawer, menu, dialog, loading block) sits on top of; a Dialog's built-in scrim is a specialised case of this primitive, and both draw from one colour source (reuse-first — dialog.rs imports the constant, does not re-declare it).

R2 — Theme-invariant colour

The scrim colour is the same in light and dark — a dimming veil is not a surface token, so it does not flip with the theme (Material parity). It is rgba(0,0,0,0.70) (black at ~178/255 alpha). This is the single source of the scrim colour across the Stack; overlays MUST NOT hard-code their own backdrop alpha.

R3 — Inertness (pointer)

The scrim swallows every pointer event over it — moves, hovers, presses — so nothing stacked beneath it reacts while the overlay is up. This is what "makes the background inert" means concretely: the page behind cannot be clicked or hovered through the veil.

R4 — Dismissal

By default a press on the scrim — or the Escape key — dismisses it, firing on_dismiss (equivalent to the overlay's Cancel). A blocking backdrop opts out with non_dismissible: true (a loading block, or a modal the owner closes only via an explicit action); then press/Escape do not dismiss and the owner drives the close.

R5 — Blocking / loading backdrop

A blocking loading state is a non_dismissible scrim with the loading content (spinner + optional caption) composed on top of it — the scrim is the veil, not the content. The scrim itself renders no caption; the overlay above it owns any text.

R6 — Layering (z-index)

The scrim sits directly below its overlay's content and above everything else on the surface. Exactly one scrim is active per overlay; overlays do not stack their own scrims (a second overlay reuses/rises above the same backdrop rather than double-dimming).

R7 — Accessibility

The scrim is decorative — it exports no semantics node (it is a veil, not content), so assistive tech does not announce it; its inertness (R3) is what removes the background from the interaction surface. Focus-trap is the overlay's responsibility, not the bare scrim: the dialog/sheet on top confines focus to its own content (dialogs.kmd R1) while the scrim holds the pointer-inert backdrop. Dismissal via Escape (R4) is the keyboard affordance the scrim itself provides.

R8 — Motion

On entry the backdrop fades the veil in (medium duration, per interaction/states.kmd); Dialog animates this same colour on open. A flat, already-present scrim (e.g. a synchronous loading block) MAY paint it without a fade.

R9 — gov.br parity

Satisfies the gov.br scrim first-class component (govbr-ds-parity, GVB-24): a dimming backdrop with dismiss-on-click, a blocking (loading) variant, and background inertness.

Não-escopo

  • The overlay's own content, layout, and focus-trap — that belongs to the Dialog / sheet / menu on top (dialogs.kmd), not the scrim.
  • Per-overlay entry/exit choreography beyond the veil fade — motion of the content is the overlay's concern.

Requirements (testable)

Requirement: Translucent-black veil with theme-invariant colour {#req-scrim-veil-colour}

A scrim SHALL fill its full bounds with a single translucent-black wash of rgba(0,0,0,0.70) and paint nothing else, and this colour SHALL be identical in light and dark themes drawn from the single source constant that overlays reuse rather than hard-coding their own backdrop alpha.

Scenario: Scrim paints the veil over its full bounds

  • GIVEN a scrim mounted over a surface region
  • WHEN the scrim renders
  • THEN every pixel of its bounds is filled with rgba(0,0,0,0.70)
  • AND no caption, border, icon, or other content is drawn by the scrim itself

Scenario: Colour does not flip with the theme

  • GIVEN a scrim rendered in light theme with fill colour rgba(0,0,0,0.70)
  • WHEN the surface theme is switched to dark
  • THEN the scrim fill colour remains exactly rgba(0,0,0,0.70)

Scenario: Overlays draw from the single colour source

  • GIVEN a Dialog whose built-in scrim reuses the shared scrim colour constant
  • WHEN the dialog's backdrop is rendered
  • THEN its backdrop colour equals the scrim primitive's rgba(0,0,0,0.70)
  • AND the dialog code imports the constant rather than declaring its own alpha value

Requirement: Pointer inertness of the background {#req-scrim-inertness}

A scrim SHALL swallow every pointer event that lands on it — moves, hovers, and presses — so that no element stacked beneath the scrim receives pointer input or reacts while the overlay is up.

Scenario: Press behind the scrim does not reach the background

  • GIVEN an interactive button positioned beneath an active scrim
  • WHEN a pointer press occurs at the button's location over the scrim
  • THEN the scrim consumes the event
  • AND the button beneath does not fire its press handler

Scenario: Hover behind the scrim does not reach the background

  • GIVEN a hoverable element beneath an active scrim
  • WHEN the pointer moves over that element's location through the scrim
  • THEN the scrim consumes the move
  • AND the element beneath does not enter its hover state

Requirement: Dismissal and blocking opt-out {#req-scrim-dismissal}

By default a press on the scrim or the Escape key SHALL dismiss it and fire on_dismiss; a scrim configured with non_dismissible: true SHALL NOT dismiss on press or Escape, leaving the owner to drive the close.

Scenario: Press dismisses a default scrim

  • GIVEN a default scrim with an on_dismiss handler and non_dismissible unset
  • WHEN the scrim receives a pointer press
  • THEN on_dismiss fires once

Scenario: Escape dismisses a default scrim

  • GIVEN a default scrim with an on_dismiss handler and non_dismissible unset
  • WHEN the Escape key is pressed
  • THEN on_dismiss fires once

Scenario: Blocking backdrop ignores press and Escape

  • GIVEN a scrim configured with non_dismissible: true
  • WHEN the scrim receives a pointer press
  • AND the Escape key is pressed
  • THEN on_dismiss does not fire
  • AND the scrim remains present

Requirement: Single scrim layered directly below its overlay {#req-scrim-layering}

A scrim SHALL sit directly below its overlay's content and above every other element on the surface, and exactly one scrim SHALL be active per overlay so that a second overlay reuses or rises above the same backdrop rather than stacking a second scrim.

Scenario: Second overlay does not double-dim the surface

  • GIVEN an overlay already showing above a single active scrim
  • WHEN a second overlay opens on the same surface
  • THEN exactly one scrim remains active on the surface
  • AND the veil is not composited twice (its effective alpha stays 0.70)

Requirement: Decorative accessibility with keyboard dismissal {#req-scrim-a11y}

The scrim SHALL be decorative and export no semantics node so assistive technology does not announce it; the scrim SHALL NOT implement a focus-trap (that is the overlay's responsibility) and SHALL provide Escape as its own keyboard dismissal affordance when dismissible.

Scenario: Scrim exposes no semantics node

  • GIVEN an active scrim on a surface
  • WHEN the accessibility tree for the surface is inspected
  • THEN the scrim contributes no semantics node
  • AND assistive technology announces nothing for the scrim

Scenario: Escape is the scrim's keyboard affordance

  • GIVEN a dismissible scrim that implements no focus-trap of its own
  • WHEN the Escape key is pressed
  • THEN the scrim dismisses via on_dismiss
  • AND focus confinement is left to the overlay on top rather than the scrim

Referências