diff --git a/.changeset/fix-focus-visible-data-attr.md b/.changeset/fix-focus-visible-data-attr.md new file mode 100644 index 0000000000..5e4f192d4f --- /dev/null +++ b/.changeset/fix-focus-visible-data-attr.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed focus ring styles to use React Aria's `[data-focus-visible]` data attribute instead of the native CSS `:focus-visible` pseudo-class. This ensures keyboard focus rings render reliably when focus is managed programmatically by React Aria (e.g. inside a GridList, Menu, or Select). + +**Affected components:** Accordion, Button, ButtonIcon, ButtonLink, Card, List, Menu, Select, ToggleButtonGroup diff --git a/packages/ui/src/components/Accordion/Accordion.module.css b/packages/ui/src/components/Accordion/Accordion.module.css index 23b527c2e7..83ae6c24b8 100644 --- a/packages/ui/src/components/Accordion/Accordion.module.css +++ b/packages/ui/src/components/Accordion/Accordion.module.css @@ -53,7 +53,7 @@ cursor: pointer; text-align: left; - &:focus-visible { + &[data-focus-visible] { outline: none; transition: none; box-shadow: inset 0 0 0 2px var(--bui-ring); diff --git a/packages/ui/src/components/Button/Button.module.css b/packages/ui/src/components/Button/Button.module.css index 322a65279d..8273e77d5a 100644 --- a/packages/ui/src/components/Button/Button.module.css +++ b/packages/ui/src/components/Button/Button.module.css @@ -73,7 +73,7 @@ --fg: var(--bui-fg-solid-disabled); } - &:focus-visible { + &[data-focus-visible] { outline: 2px solid var(--bui-ring); outline-offset: 2px; } @@ -106,7 +106,7 @@ --bg-active: var(--bg-solid-danger-disabled); } - &:focus-visible { + &[data-focus-visible] { outline: 2px solid var(--bui-border-danger); outline-offset: 2px; } @@ -143,7 +143,7 @@ --fg: var(--bui-fg-disabled); } - &:focus-visible { + &[data-focus-visible] { outline: none; transition: none; box-shadow: inset 0 0 0 2px var(--bui-ring); @@ -172,7 +172,7 @@ --fg: var(--bui-fg-disabled); } - &:focus-visible { + &[data-focus-visible] { box-shadow: inset 0 0 0 2px var(--bui-border-danger); } } @@ -204,7 +204,7 @@ --fg: var(--bui-fg-disabled); } - &:focus-visible { + &[data-focus-visible] { outline: none; transition: none; box-shadow: inset 0 0 0 2px var(--bui-ring); @@ -232,7 +232,7 @@ --fg: var(--bui-fg-disabled); } - &:focus-visible { + &[data-focus-visible] { box-shadow: inset 0 0 0 2px var(--bui-border-danger); } } diff --git a/packages/ui/src/components/ButtonIcon/ButtonIcon.module.css b/packages/ui/src/components/ButtonIcon/ButtonIcon.module.css index 128e958594..657b143ed5 100644 --- a/packages/ui/src/components/ButtonIcon/ButtonIcon.module.css +++ b/packages/ui/src/components/ButtonIcon/ButtonIcon.module.css @@ -73,7 +73,7 @@ --fg: var(--bui-fg-solid-disabled); } - &:focus-visible { + &[data-focus-visible] { outline: 2px solid var(--bui-ring); outline-offset: 2px; } @@ -110,7 +110,7 @@ --fg: var(--bui-fg-disabled); } - &:focus-visible { + &[data-focus-visible] { outline: none; transition: none; box-shadow: inset 0 0 0 2px var(--bui-ring); @@ -144,7 +144,7 @@ --fg: var(--bui-fg-disabled); } - &:focus-visible { + &[data-focus-visible] { outline: none; transition: none; box-shadow: inset 0 0 0 2px var(--bui-ring); diff --git a/packages/ui/src/components/ButtonLink/ButtonLink.module.css b/packages/ui/src/components/ButtonLink/ButtonLink.module.css index 74a3fe6ba6..3c221ab878 100644 --- a/packages/ui/src/components/ButtonLink/ButtonLink.module.css +++ b/packages/ui/src/components/ButtonLink/ButtonLink.module.css @@ -67,7 +67,7 @@ --fg: var(--bui-fg-solid-disabled); } - &:focus-visible { + &[data-focus-visible] { outline: 2px solid var(--bui-ring); outline-offset: 2px; } @@ -103,7 +103,7 @@ --fg: var(--bui-fg-disabled); } - &:focus-visible { + &[data-focus-visible] { outline: none; transition: none; box-shadow: inset 0 0 0 2px var(--bui-ring); @@ -136,7 +136,7 @@ --fg: var(--bui-fg-disabled); } - &:focus-visible { + &[data-focus-visible] { outline: none; transition: none; box-shadow: inset 0 0 0 2px var(--bui-ring); diff --git a/packages/ui/src/components/Card/Card.module.css b/packages/ui/src/components/Card/Card.module.css index 231c6c55e0..eced185550 100644 --- a/packages/ui/src/components/Card/Card.module.css +++ b/packages/ui/src/components/Card/Card.module.css @@ -69,7 +69,7 @@ } } - .bui-Card[data-interactive]:has(.bui-CardTrigger:focus-visible) { + .bui-Card[data-interactive]:has(.bui-CardTrigger[data-focus-visible]) { outline: 2px solid var(--bui-ring); outline-offset: -2px; } diff --git a/packages/ui/src/components/List/List.module.css b/packages/ui/src/components/List/List.module.css index f41b086618..9968f0b5bc 100644 --- a/packages/ui/src/components/List/List.module.css +++ b/packages/ui/src/components/List/List.module.css @@ -19,7 +19,6 @@ @layer components { .bui-List { box-sizing: border-box; - overflow-y: auto; outline: none; display: flex; flex-direction: column; @@ -30,7 +29,7 @@ gap: 0; } - &:focus-visible { + &[data-focus-visible] { outline: none; } } @@ -51,6 +50,10 @@ color: var(--bui-fg-disabled); } + &[data-focus-visible] { + outline: 2px solid var(--bui-ring); + } + &[data-selection-mode] { cursor: pointer; padding-block: var(--bui-space-2); diff --git a/packages/ui/src/components/Menu/Menu.module.css b/packages/ui/src/components/Menu/Menu.module.css index e16f87ee5a..9acedd106e 100644 --- a/packages/ui/src/components/Menu/Menu.module.css +++ b/packages/ui/src/components/Menu/Menu.module.css @@ -78,7 +78,7 @@ padding-inline: var(--bui-space-1); display: block; - &:focus-visible { + &[data-focus-visible] { outline: none; } diff --git a/packages/ui/src/components/Select/Select.module.css b/packages/ui/src/components/Select/Select.module.css index 74801de1a9..c5ce30e363 100644 --- a/packages/ui/src/components/Select/Select.module.css +++ b/packages/ui/src/components/Select/Select.module.css @@ -135,7 +135,7 @@ padding-block: var(--bui-space-1); padding-inline: var(--bui-space-1); - &:focus-visible { + &[data-focus-visible] { /* Remove default focus-visible outline because React Aria * triggers it on mouse click open of the list for some reason. * On keyboard use, the top item receives the focus style, diff --git a/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css index fef9b33a9a..827b7f18d6 100644 --- a/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css +++ b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.module.css @@ -102,7 +102,7 @@ } /* Focus ring on group surface */ - .bui-ToggleButtonGroup:focus-visible { + .bui-ToggleButtonGroup[data-focus-visible] { outline: 2px solid var(--bui-ring); outline-offset: 2px; }