Skip to main contentSkip to docs navigation

Focus ring

Opt-in keyboard focus indicator that replaces the browser default with a styled outline built from CSS custom properties for width, color, and offset.

@layer helpers
Context tokens

.focus-ring applies an outline on :focus-visible, replacing the browser's unstyled default indicator. The ring reads its width, color, and offset from CSS custom properties set at :root, so overriding those properties on any ancestor element scopes the change to that subtree. Because the selector targets :focus-visible, the ring appears during keyboard navigation and stays hidden on pointer focus.

HTML
<a href="#" class="d-inline-flex focus-ring py-2xsmall px-xsmall text-decoration-none border rounded">
  Custom focus ring
</a>

Context variants

.focus-ring derives its color from --focus-ring-color, which reads --cue-color from the cascade. Placing .focus-ring inside a context class automatically shifts the ring to that context's cue color.

HTML
<p><a href="#" class="focus-ring context default py-2xsmall px-xsmall text-decoration-none border rounded">Default focus</a></p>
<p><a href="#" class="focus-ring context alternate py-2xsmall px-xsmall text-decoration-none border rounded">Alternate focus</a></p>
<p><a href="#" class="focus-ring context primary py-2xsmall px-xsmall text-decoration-none border rounded">Primary focus</a></p>
<p><a href="#" class="focus-ring context secondary py-2xsmall px-xsmall text-decoration-none border rounded">Secondary focus</a></p>
<p><a href="#" class="focus-ring context neutral py-2xsmall px-xsmall text-decoration-none border rounded">Neutral focus</a></p>
<p><a href="#" class="focus-ring context danger py-2xsmall px-xsmall text-decoration-none border rounded">Danger focus</a></p>
<p><a href="#" class="focus-ring context success py-2xsmall px-xsmall text-decoration-none border rounded">Success focus</a></p>
<p><a href="#" class="focus-ring context warning py-2xsmall px-xsmall text-decoration-none border rounded">Warning focus</a></p>
<p><a href="#" class="focus-ring context info py-2xsmall px-xsmall text-decoration-none border rounded">Info focus</a></p>
<p><a href="#" class="focus-ring context black py-2xsmall px-xsmall text-decoration-none border rounded">Black focus</a></p>
<p><a href="#" class="focus-ring context white py-2xsmall px-xsmall text-decoration-none border rounded">White focus</a></p>

Customization

Override --focus-ring-* properties inline or on a parent element to adjust the ring for a specific component without changing the global defaults.

HTML
<a href="#" class="d-inline-flex focus-ring py-2xsmall px-xsmall text-decoration-none border rounded" style="
    --cx-focus-ring-color: color-mix(in oklch, var(--cx-primary) 50%, var(--cx-secondary));
    --cx-focus-ring-width: .25rem;
    --cx-focus-ring-offset: .125rem;
    ">
  Custom focus ring
</a>

CSS

.focus-ring calls the focus-ring() mixin on :focus-visible, emitting an outline plus outline-offset from the ring custom properties.

.focus-ring:focus-visible {
  @include focus-ring(true);
}

Custom properties

Five custom properties on :root govern the ring. --focus-ring-color reads --cue-color from the cascade, so the ring adapts automatically inside context classes. --focus-ring-light-opacity and --focus-ring-dark-opacity control the transparency of the cue color in each color scheme.

--focus-ring-width: #{$focus-ring-width};
--focus-ring-light-opacity: #{$focus-ring-light-opacity};
--focus-ring-dark-opacity: #{$focus-ring-dark-opacity};
--focus-ring-color: #{$focus-ring-color};
--focus-ring-offset: #{$focus-ring-offset};
// --focus-ring: #{$focus-ring};

Sass variables

Sass variables compile the initial values for the :root custom properties above.

$focus-ring-width:          .1875rem;
$focus-ring-light-opacity:  .4;
$focus-ring-dark-opacity:   .6;
$focus-ring-color:          light-dark(to-opacity(var(--cue-color), var(--focus-ring-light-opacity)), to-opacity(var(--cue-color), var(--focus-ring-dark-opacity)));
// $focus-ring:                var(--focus-ring-width) solid var(--focus-ring-color);
$focus-ring-offset:         0px;

Sass mixin

focus-ring() emits the outline declaration for use in custom selectors. Pass true as the first argument to include outline-offset; pass a color string as the second to hard-code a ring color instead of reading --focus-ring-color.

SCSS
.custom-selector {
  &:focus-visible {
    @include focus-ring(true);
  }
}
@mixin focus-ring($offset: false, $color: null) {
  @if $color != null {
    outline: var(--focus-ring-width) solid #{$color};
  } @else {
    outline: var(--focus-ring-width) solid var(--focus-ring-color);
  }
  @if $offset {
    outline-offset: var(--focus-ring-offset);
  }
}