Start with a narrow acceptance boundary
The useful unit of work isn't "make focus accessible." That scope is too broad to assign or verify. Name one focus-indicator contract, its governing source and version, its supported environments and consumers, and the surfaces where it must remain inspectable. Mark everything outside that boundary as unresolved or not applicable, with a reason.
For example, a bounded review might cover the primary button, inline link, text input, checkbox, and tabs in one web application. It could include the application's light and dark themes, ordinary and contrasting surfaces, currently supported browsers, and forced colors. It would not cover every operating system, browser, component, or assistive-technology combination unless someone actually tested them.
Do not turn scope into proof
Writing "supports forced colors" in a contract records an expectation. It becomes an observation only after someone inspects the named consumer in the recorded environment. Neither state by itself establishes accessibility conformance.
Give every decision one enforceable home
Focus color is often semantic because several controls need to share the same intent across themes. Geometry can also be reusable when a system standardizes outline width, offset, style, or a combined treatment. HPE's global focusIndicator is one example of reusable geometry living above individual components. A component contract should own a decision only when a named component genuinely needs behavior narrower than the shared rule.
| Decision owned | Where it is enforced | Accountable owner | Failure signal | |
|---|---|---|---|---|
| Semantic color and shared intent | Light and dark focus role, plus any approved role relationship | Token source and emitted semantic artifacts | Design-system token owner | Wrong role, wrong mode mapping, missing export, or stale artifact |
| Reusable geometry | Shared outline width, style, offset, or another reusable treatment when the system standardizes it | Global or semantic token contract and generated output | Design-system foundation owner | Shared geometry differs without an approved component exception |
| Component-specific behavior | Variant geometry, clipping avoidance, internal focus treatment, or composite-control behavior | Component contract and component implementation | Component owner | One component or variant diverges while the shared inputs are correct |
| Selector and fallback behavior | :focus, :focus-visible, fallback boundary, and rule ordering | Application or library CSS | CSS implementation owner | The intended selector does not match, the fallback is removed, or cascade order suppresses the indicator |
| Forced-color handling | Media-query branch, system-color choice, and treatment of effects that may be suppressed | CSS under @media (forced-colors: active) | Accessibility and CSS owners | The indicator disappears, becomes indistinguishable, or depends on a suppressed effect |
| Approved exception | Exact consumer, reason, replacement behavior, approver, and expiry or review condition | Exception register plus narrow component or product rule | Named exception owner | An override has no record, spreads to other consumers, or outlives its review condition |
| Unresolved authority | A missing source decision, disputed owner, or unsupported environment | Decision register, not production CSS disguised as policy | Person assigned to resolve the decision | Implementation proceeds through guesswork or an accidental local default |
Read HPE, Carbon, and NYS as examples, not a shared standard
Public design systems solve similar problems with different structures. Their names describe their own systems; they are not portable names that every project must copy.
| Observed approach | Useful lesson | Do not universalize | |
|---|---|---|---|
| HPE | A global focusIndicator groups a standardized treatment used across its design system. | Reusable focus geometry can be governed above individual components when one shared contract is intentional. | The HPE token name, package shape, values, or ownership model. |
| Carbon | Focus-related roles appear inside a larger catalog organized for Carbon's interface system. | A mature system may need several roles rather than one generic focus color. | Carbon's role names or the assumption that its catalog maps directly to another component library. |
| NYS | Primitive values feed purpose-driven semantic roles, including a semantic focus color, with theming as another layer. | Consumers should depend on purpose rather than a raw palette value when the role must survive theme changes. | The NYS variable name, layer boundaries, or theme mechanics outside an NYS implementation. |
A portable method begins above those system-specific choices: record the purpose, authority, mapping, consumers, evidence, and disposition. Your project can then adopt, translate, or reject a vendor's structure without treating the vendor's terminology as a web-wide convention.
Copy the focus-indicator contract before writing CSS
The contract below is long on purpose. A field may be required, conditional, unresolved, or not applicable, but it should not disappear. Missing fields are where assumptions turn into local overrides.
focusIndicatorContract:
contractId: required
governingSource:
name: required
version: required
authorityOwner: required
semanticPurpose: required
supportedEnvironments:
browsersAndVersions: required
platforms: required
forcedColors: required | not_applicable_with_reason
modes:
lightMapping: required
darkMapping: required
additionalThemes: conditional
geometry:
sharedTreatment: required | unresolved
componentOverrides: conditional
selectorPolicy:
focusRule: required
focusVisibleRule: required
unsupportedSelectorFallback: required
keyboardExpectation: required
pointerExpectation: required
programmaticFocusExpectation: required
forcedColorPolicy:
affectedPropertyRisk: required
systemColorFallback: required | unresolved
namedConsumers: required
protectedSurfaces: required
approvedExceptions: required | none
owners:
tokens: required
components: required
css: required
verification: required
evidence:
sourceDecision: required
artifactIdentity: required
projectMapping: required | unresolved
runtimeObservations: required | open
retestTriggers: required
disposition: accept | revise | blockProtected surfaces should be concrete: a primary button on the default page background, an inline link inside a card, or tabs inside a contrasting navigation panel. "All surfaces" is not inspectable. Named consumers and representative surfaces keep verification finite without implying that untested consumers passed.
Use Ambient Sage v1 as bounded upstream intake
The public Ambient Sage page establishes a versioned upstream artifact: Ambient Sage v1, a semantic ring role, light and dark token sets, focus-ring direction, and implementation exports. It also describes vivid yellow as the saturated accent used for focus rings. Those facts belong in a project contract. They do not establish the project's alias name, selector policy, component adoption, forced-color branch, runtime result, or accessibility disposition.
Token specimen · real values
Ambient Sage
Live renderAmbient Sage's actual tokens — the same values its exports use.
Evidence state for this example
Upstream source and version: established. Ring role and available exports: established. Project alias, selector behavior, named component mapping, forced-color behavior, and rendered observations: unresolved until a consuming project records them.
Trace one ring role from source to a named consumer
The following chain is illustrative. Only the Ambient Sage v1 source role and export availability come from the frozen public kit evidence. Every downstream identifier is a proposed local mapping, so it remains unresolved until the project confirms it.
- 1
Pin the governing source
Record Ambient Sage v1 and the public kit page as the authority for the upstream semantic ring role. Do not replace that versioned source with a screenshot or copied literal.
source: Ambient Sage v1 role: ring status: established - 2
Identify the emitted artifact
Choose the export the project actually consumes and record its file identity or generated version. Identity Forge offers DESIGN.md, CSS, Tailwind, DTCG, shadcn, CLI, and MCP routes, but availability does not prove which one a project used.
artifact: unresolved expectedRole: ring status: open - 3
Declare the project alias
Map the incoming role to one local semantic property. The name below is an example, not an observed Ambient Sage export.
--app-focus-ring: var(--ring); - 4
Apply selector policy
Keep a visible :focus fallback, then refine it with :focus-visible where supported. Record expected behavior by focus path instead of assuming that the device type determines the match.
.control:focus { outline: var(--app-focus-width) solid var(--app-focus-ring); outline-offset: var(--app-focus-offset); } .control:focus:not(:focus-visible) { outline: none; } .control:focus-visible { outline: var(--app-focus-width) solid var(--app-focus-ring); outline-offset: var(--app-focus-offset); } - 5
Bind the component contract
For the primary Button, record whether it inherits the shared geometry or owns an approved override. Put clipping, border-radius interaction, and any variant-specific rule in the component contract instead of hiding them in a local selector.
consumerContract: Button.primary sharedGeometry: unresolved approvedOverride: none_recorded status: open - 6
Inspect the named consumer
Render the primary Button on one named surface under every in-scope condition. Record its computed styles and visible result. Until then, the trace ends with an open observation.
consumer: Button.primary surface: dashboard/default actualObservation: open disposition: block
This trace prevents a common false conclusion. Finding the ring role in an exported file proves that the artifact contains the role. It does not prove that the application loads the artifact, the local alias resolves, the selector matches, the component preserves the outline, or the rendered indicator remains distinguishable on its surface.
Use :focus-visible as a heuristic boundary
The :focus-visible pseudo-class matches a focused element when the user agent decides that a focus indicator should appear. Keyboard interaction commonly influences that decision, but "keyboard means match" and "pointer means no match" are not reliable universal rules. User preferences, element type, focus movement, browser behavior, and the way focus was assigned can all affect the result.
| Expectation to record | What to inspect | Failure condition | |
|---|---|---|---|
| Keyboard path | The focused control has a visible indicator when :focus-visible matches. | Selector match, computed outline or replacement treatment, clipping, and surface distinction. | No visible indicator on an in-scope keyboard path, or the indicator is obscured or clipped. |
| Pointer path | Do not promise suppression. Record whether :focus-visible matches in each supported environment. | Focused element, matched selectors, and any remaining :focus fallback. | The observed result contradicts the project's documented policy or removes an indicator required by that policy. |
| Programmatic focus | State which scripted transitions are in scope, such as focus moved into an opened dialog or to a validation target. | Calling flow, focused element, selector match, and resulting indicator. | Focus moves but the expected indicator is absent, or focus lands on the wrong consumer. |
A practical fallback begins with :focus. Remove or replace that treatment only inside a selector using :focus-visible. If a browser does not support :focus-visible, it ignores the unsupported refinement and keeps the :focus rule. Test the exact rule ordering and supported-browser set; copying the pattern does not prove that the cascade works in your application.
Keep the fallback visible
Do not begin with outline: none and assume that a later :focus-visible rule will repair every case. The fallback boundary should retain a visible :focus treatment when the refinement is unsupported or does not apply as expected.
Create an explicit forced-colors branch
Forced-colors mode changes the rendering environment. The browser may force colors on several properties, while altering or suppressing decorative effects. A focus treatment that depends on box-shadow alone is therefore risky: its visual separation may disappear even when the ordinary theme looks correct.
Use the forced-colors media feature to define a bounded fallback, then inspect it. System colors such as Highlight follow colors selected by the user agent or operating environment. They are a suitable candidate for a focus outline in this branch, but choosing one in CSS remains an expectation. It does not prove that every consumer renders correctly.
@media (forced-colors: active) {
.control:focus-visible {
box-shadow: none;
outline: var(--app-focus-width) solid Highlight;
outline-offset: var(--app-focus-offset);
}
}Record four facts for this branch: which authored effects may be lost, which system color is selected, which consumers were inspected, and what happened. A global media query may be insufficient if a composite control draws focus on an internal item while its container clips outlines. That requires a component-specific contract or an approved exception; it is not evidence that the token failed.
Verify representative controls and surfaces
Prepare the matrix before testing so the expectations cannot drift to match a screenshot. Each row below is a test specification, not a completed benchmark. Replace "Open" with an environment-specific observation only after inspection.
| Test content and conditions | Inspect | Failure condition | Observation and owner | |
|---|---|---|---|---|
| Primary button | Label: Save changes. Keyboard, pointer, and programmatic focus on default and contrasting surfaces in light and dark modes; repeat in forced colors. | Focused element, :focus and :focus-visible matches, resolved ring role, outline geometry, clipping, and disabled-state exclusion. | Expected indicator is absent, clipped, obscured, mapped to the wrong mode value, or shown on a non-focusable disabled control. | Open. Component owner for button behavior; CSS owner for selector or forced-color divergence. |
| Inline link | Text: Review billing details within a paragraph. Test light and dark content surfaces, keyboard and pointer paths, then forced colors. | Indicator remains distinguishable from link styling and surrounding text; inspect selector match and any outline clipping caused by line layout. | Focus is indicated only by an indistinguishable color change, the indicator is clipped, or the fallback disappears. | Open. Typography or link-component owner, with CSS owner for selector behavior. |
| Text input | Label: Work email; value: alex@example.com; include normal and validation-error presentations. Test both modes and forced colors. | Focus treatment can be distinguished from the existing border and error treatment; inspect resolved role, geometry, and cascade order. | Focus state is hidden by the border or error style, replaces required error information, or disappears in forced colors. | Open. Input owner for state composition; token owner if the mapped role is wrong. |
| Checkbox | Label: Send me release notes. Test checked and unchecked controls on default and contrasting surfaces in both modes. | Indicator belongs to the actual focus target, remains visible around its small geometry, and is not confused with checked state. | Checked styling is the only visible change, focus is drawn on the wrong element, or the indicator is clipped. | Open. Checkbox owner for target and geometry; CSS owner for selector behavior. |
| Composite tabs | Tabs: Overview, Activity, Settings. Move focus among items through the component's supported keyboard interaction; repeat in both modes and forced colors. | Distinguish focused item from selected item; inspect the active descendant or focused element, selector match, overflow clipping, and system-color result. | Selection is mistaken for focus, focus moves without a visible indicator, multiple items appear focused, or the container clips the treatment. | Open. Composite-control owner for focus management; CSS owner for rendering. |
Why these controls
This is a representative set, not a claim of complete coverage. It includes text, compact controls, bordered controls, and a composite widget where selected and focused states can diverge. Add another consumer only when its focus contract differs materially.
Route failures to the first divergent layer
A rendered mismatch sits at the end of a chain. Correct it at the first layer that differs from the approved contract, then retest every downstream layer affected by the correction.
- 1
Confirm source authority
Check the governing source, version, approved semantic purpose, mode mappings, and reusable geometry. If authority is missing or disputed, stop and assign the decision. Do not let local CSS settle it by accident.
- 2
Inspect the emitted artifact
Identify the exact export the project uses. Confirm that the expected role and values appear in that artifact. A correct source paired with a stale or incomplete export is an artifact problem.
- 3
Resolve the project mapping
Follow the emitted role into the local alias and both mode branches. If the alias points elsewhere or an override applies at the wrong scope, the project mapping is the first divergence.
- 4
Check selector and environmental CSS
Inspect rule order, specificity, the :focus fallback, :focus-visible matching, and the forced-colors branch. Record the browser and environment instead of generalizing from one result.
- 5
Check the component contract
Confirm that the named component consumes the shared rule or has an approved narrow override. Inspect overflow, borders, shadows, internal focus targets, and state composition.
- 6
Record the consumer observation
Record expected and actual results separately. Assign the correction owner at the first divergent layer, name the change that triggers a retest, and set the disposition to accept, revise, or block.
If the upstream role is correct, the artifact contains it, and the project alias resolves, changing the source color is unlikely to restore a missing ring. Check the selector, cascade, component overflow, or focus target. Conversely, trace a ring rendered with the wrong light or dark value through the local mapping and emitted artifact before adding a component override.
Join expectations and observations in one acceptance record
The acceptance record closes the loop. Keep source facts, proposed mappings, observations, exceptions, and decisions in separate fields so reviewers can see exactly what remains open.
focusIndicatorAcceptance:
contractId: focus-web-v1
sourceAuthority:
source: Ambient Sage
version: v1
upstreamRingRole: established
artifactIdentity:
format: unresolved
fileOrBuildId: unresolved
observedRole: open
projectMapping:
alias: --app-focus-ring
status: illustrative_unverified
consumer:
component: Button.primary
surface: dashboard/default
environment:
browserAndVersion: unresolved
theme: light
focusPath: keyboard
forcedColors: inactive
expectedResult:
selectorPolicy: use_focus_fallback_then_focus_visible_refinement
visibleTreatment: required
geometry: unresolved
actualResult:
selectorMatches: open
computedStyles: open
visualInspection: open
exception:
id: none_recorded
owner: not_applicable
correctionOwner: unresolved_until_first_divergence
retestTrigger: any_change_to_source_artifact_mapping_css_or_button_contract
disposition: blockUse accept only when every required field within the stated boundary has evidence and no blocking mismatch remains. Use revise when the contract or implementation needs a named correction that can be retested. Use block when required authority or evidence is missing, the first divergence has no owner, or an in-scope consumer fails its expectation. Optional evidence may be marked not applicable with a reason; required evidence may not.
Start with a versioned upstream system
If your project still lacks semantic light and dark roles or an identifiable implementation artifact, inspect the free Ambient Sage kit as an upstream example. Then complete the local mapping, selector policy, component contract, and observations in your own acceptance record.
Sources
- Global - HPE Design System: HPE documents a system-wide focusIndicator token that combines focus-related values within HPE's own global-token structure.
- Color Tokens - Carbon Design System: Carbon documents color and focus roles within Carbon's own token catalog and naming system.
- Design Tokens - NYS Design System: The NYS Design System separates primitive, semantic, and theme-token layers and includes a semantic focus color role.
- :focus-visible CSS pseudo-class - MDN Web Docs: MDN documents that :focus-visible matching depends on user-agent heuristics and provides a :focus-based fallback pattern.
- :focus CSS pseudo-class - MDN Web Docs: MDN documents :focus as matching an element that has received focus and warns against removing a visible outline without a replacement.
- forced-colors CSS media feature - MDN Web Docs: MDN documents the forced-colors media feature and the browser adjustments that can change or suppress authored visual properties.
- <system-color> CSS type - MDN Web Docs: MDN documents system colors for authored properties that need to follow user-selected colors, including forced-color conditions.
- Ambient Sage Design Kit: The public Ambient Sage v1 kit establishes a semantic ring role, light and dark token sets, focus-ring direction, and downloadable implementation artifacts.