diff --git a/.changeset/strange-jobs-add.md b/.changeset/strange-jobs-add.md new file mode 100644 index 00000000000..4dba14fd7c0 --- /dev/null +++ b/.changeset/strange-jobs-add.md @@ -0,0 +1,5 @@ +--- +"@primer/react": patch +--- + +Add button focus styles diff --git a/src/Button/styles.ts b/src/Button/styles.ts index a38b80b0b61..31f1ded7954 100644 --- a/src/Button/styles.ts +++ b/src/Button/styles.ts @@ -2,6 +2,17 @@ import {VariantType} from './types' import {Theme} from '../ThemeProvider' export const TEXT_ROW_HEIGHT = '20px' // custom value off the scale +const focusOutlineStyles = { + outline: '2px solid', + outlineColor: 'accent.fg', + outlineOffset: '-2px' +} +const fallbackFocus = { + ...focusOutlineStyles, + ':not(:focus-visible)': { + outline: 'solid 1px transparent' + } +} export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme) => { const style = { @@ -14,8 +25,9 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme }, // focus must come before :active so that the active box shadow overrides '&:focus:not([disabled])': { - boxShadow: `${theme?.shadows.btn.focusShadow}` + ...fallbackFocus }, + '&:focus-visible:not([disabled])': focusOutlineStyles, '&:active:not([disabled])': { backgroundColor: 'btn.activeBg', borderColor: 'btn.activeBorder' @@ -42,7 +54,12 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme }, // focus must come before :active so that the active box shadow overrides '&:focus:not([disabled])': { - boxShadow: `${theme?.shadows.btn.primary.focusShadow}` + boxShadow: 'inset 0 0 0 3px', + ...fallbackFocus + }, + '&:focus-visible:not([disabled])': { + ...focusOutlineStyles, + boxShadow: 'inset 0 0 0 3px' }, '&:active:not([disabled])': { backgroundColor: 'btn.primary.selectedBg', @@ -80,9 +97,9 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme }, // focus must come before :active so that the active box shadow overrides '&:focus:not([disabled])': { - borderColor: 'btn.danger.focusBorder', - boxShadow: `${theme?.shadows.btn.danger.focusShadow}` + ...fallbackFocus }, + '&:focus-visible:not([disabled])': focusOutlineStyles, '&:active:not([disabled])': { color: 'btn.danger.selectedText', backgroundColor: 'btn.danger.selectedBg', @@ -119,8 +136,9 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme }, // focus must come before :active so that the active box shadow overrides '&:focus:not([disabled])': { - boxShadow: `${theme?.shadows.btn.focusShadow}` + ...fallbackFocus }, + '&:focus-visible:not([disabled])': focusOutlineStyles, '&:active:not([disabled])': { backgroundColor: 'btn.selectedBg' }, @@ -152,10 +170,9 @@ export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme }, // focus must come before :active so that the active box shadow overrides '&:focus:not([disabled])': { - borderColor: 'btn.outline.focusBorder', - boxShadow: `${theme?.shadows.btn.outline.focusShadow}` + ...fallbackFocus }, - + '&:focus-visible:not([disabled])': focusOutlineStyles, '&:active:not([disabled])': { color: 'btn.outline.selectedText', backgroundColor: 'btn.outline.selectedBg', @@ -242,9 +259,6 @@ export const getBaseStyles = (theme?: Theme) => ({ userSelect: 'none', textDecoration: 'none', textAlign: 'center', - '&:focus': { - outline: 'none' - }, '&:disabled': { cursor: 'default' }, diff --git a/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap b/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap index 6a1ffc5e47e..a73796df9c5 100644 --- a/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap +++ b/src/__tests__/__snapshots__/ActionMenu.test.tsx.snap @@ -44,10 +44,6 @@ exports[`ActionMenu renders consistently 1`] = ` box-shadow: 0 1px 0 rgba(27,31,36,0.04),inset 0 1px 0 rgba(255,255,255,0.25); } -.c1:focus { - outline: none; -} - .c1:disabled { cursor: default; color: #8c959f; @@ -86,7 +82,19 @@ exports[`ActionMenu renders consistently 1`] = ` } .c1:focus:not([disabled]) { - box-shadow: 0 0 0 3px rgba(9,105,218,0.3); + outline: 2px solid; + outline-color: #0969da; + outline-offset: -2px; +} + +.c1:focus:not([disabled]):not(:focus-visible) { + outline: solid 1px transparent; +} + +.c1:focus-visible:not([disabled]) { + outline: 2px solid; + outline-color: #0969da; + outline-offset: -2px; } .c1:active:not([disabled]) { diff --git a/src/__tests__/__snapshots__/Button.test.tsx.snap b/src/__tests__/__snapshots__/Button.test.tsx.snap index 48a7b73120c..132275194b1 100644 --- a/src/__tests__/__snapshots__/Button.test.tsx.snap +++ b/src/__tests__/__snapshots__/Button.test.tsx.snap @@ -33,10 +33,6 @@ exports[`Button renders consistently 1`] = ` box-shadow: 0 1px 0 rgba(27,31,36,0.04),inset 0 1px 0 rgba(255,255,255,0.25); } -.c0:focus { - outline: none; -} - .c0:disabled { cursor: default; color: #8c959f; @@ -75,7 +71,19 @@ exports[`Button renders consistently 1`] = ` } .c0:focus:not([disabled]) { - box-shadow: 0 0 0 3px rgba(9,105,218,0.3); + outline: 2px solid; + outline-color: #0969da; + outline-offset: -2px; +} + +.c0:focus:not([disabled]):not(:focus-visible) { + outline: solid 1px transparent; +} + +.c0:focus-visible:not([disabled]) { + outline: 2px solid; + outline-color: #0969da; + outline-offset: -2px; } .c0:active:not([disabled]) { @@ -131,10 +139,6 @@ exports[`Button styles danger button appropriately 1`] = ` box-shadow: undefined; } -.c0:focus { - outline: none; -} - .c0:disabled { cursor: default; color: btn.danger.disabledText; @@ -186,8 +190,19 @@ exports[`Button styles danger button appropriately 1`] = ` } .c0:focus:not([disabled]) { - border-color: btn.danger.focusBorder; - box-shadow: undefined; + outline: 2px solid; + outline-color: accent.fg; + outline-offset: -2px; +} + +.c0:focus:not([disabled]):not(:focus-visible) { + outline: solid 1px transparent; +} + +.c0:focus-visible:not([disabled]) { + outline: 2px solid; + outline-color: accent.fg; + outline-offset: -2px; } .c0:active:not([disabled]) { @@ -255,10 +270,6 @@ exports[`Button styles icon only button to make it a square 1`] = ` box-shadow: undefined,undefined; } -.c0:focus { - outline: none; -} - .c0:disabled { cursor: default; color: primer.fg.disabled; @@ -281,7 +292,19 @@ exports[`Button styles icon only button to make it a square 1`] = ` } .c0:focus:not([disabled]) { - box-shadow: undefined; + outline: 2px solid; + outline-color: accent.fg; + outline-offset: -2px; +} + +.c0:focus:not([disabled]):not(:focus-visible) { + outline: solid 1px transparent; +} + +.c0:focus-visible:not([disabled]) { + outline: 2px solid; + outline-color: accent.fg; + outline-offset: -2px; } .c0:active:not([disabled]) { @@ -358,10 +381,6 @@ exports[`Button styles invisible button appropriately 1`] = ` box-shadow: none; } -.c0:focus { - outline: none; -} - .c0:disabled { cursor: default; color: primer.fg.disabled; @@ -400,7 +419,19 @@ exports[`Button styles invisible button appropriately 1`] = ` } .c0:focus:not([disabled]) { - box-shadow: undefined; + outline: 2px solid; + outline-color: accent.fg; + outline-offset: -2px; +} + +.c0:focus:not([disabled]):not(:focus-visible) { + outline: solid 1px transparent; +} + +.c0:focus-visible:not([disabled]) { + outline: 2px solid; + outline-color: accent.fg; + outline-offset: -2px; } .c0:active:not([disabled]) { @@ -461,10 +492,6 @@ exports[`Button styles outline button appropriately 1`] = ` background-color: btn.bg; } -.c0:focus { - outline: none; -} - .c0:disabled { cursor: default; color: btn.outline.disabledText; @@ -516,8 +543,19 @@ exports[`Button styles outline button appropriately 1`] = ` } .c0:focus:not([disabled]) { - border-color: btn.outline.focusBorder; - box-shadow: undefined; + outline: 2px solid; + outline-color: accent.fg; + outline-offset: -2px; +} + +.c0:focus:not([disabled]):not(:focus-visible) { + outline: solid 1px transparent; +} + +.c0:focus-visible:not([disabled]) { + outline: 2px solid; + outline-color: accent.fg; + outline-offset: -2px; } .c0:active:not([disabled]) { @@ -584,10 +622,6 @@ exports[`Button styles primary button appropriately 1`] = ` box-shadow: undefined; } -.c0:focus { - outline: none; -} - .c0:disabled { cursor: default; color: btn.primary.disabledText; @@ -630,7 +664,21 @@ exports[`Button styles primary button appropriately 1`] = ` } .c0:focus:not([disabled]) { - box-shadow: undefined; + box-shadow: inset 0 0 0 3px; + outline: 2px solid; + outline-color: accent.fg; + outline-offset: -2px; +} + +.c0:focus:not([disabled]):not(:focus-visible) { + outline: solid 1px transparent; +} + +.c0:focus-visible:not([disabled]) { + outline: 2px solid; + outline-color: accent.fg; + outline-offset: -2px; + box-shadow: inset 0 0 0 3px; } .c0:active:not([disabled]) { diff --git a/src/__tests__/__snapshots__/TextInput.test.tsx.snap b/src/__tests__/__snapshots__/TextInput.test.tsx.snap index 6c82e7e7ba0..f8408bbeaac 100644 --- a/src/__tests__/__snapshots__/TextInput.test.tsx.snap +++ b/src/__tests__/__snapshots__/TextInput.test.tsx.snap @@ -1189,10 +1189,6 @@ exports[`TextInput renders trailingAction icon button 1`] = ` box-shadow: none; } -.c3:focus { - outline: none; -} - .c3:disabled { cursor: default; color: #8c959f; @@ -1215,7 +1211,19 @@ exports[`TextInput renders trailingAction icon button 1`] = ` } .c3:focus:not([disabled]) { - box-shadow: 0 0 0 3px rgba(9,105,218,0.3); + outline: 2px solid; + outline-color: #0969da; + outline-offset: -2px; +} + +.c3:focus:not([disabled]):not(:focus-visible) { + outline: solid 1px transparent; +} + +.c3:focus-visible:not([disabled]) { + outline: 2px solid; + outline-color: #0969da; + outline-offset: -2px; } .c3:active:not([disabled]) { @@ -1632,10 +1640,6 @@ exports[`TextInput renders trailingAction text button 1`] = ` box-shadow: none; } -.c2:focus { - outline: none; -} - .c2:disabled { cursor: default; color: #8c959f; @@ -1674,7 +1678,19 @@ exports[`TextInput renders trailingAction text button 1`] = ` } .c2:focus:not([disabled]) { - box-shadow: 0 0 0 3px rgba(9,105,218,0.3); + outline: 2px solid; + outline-color: #0969da; + outline-offset: -2px; +} + +.c2:focus:not([disabled]):not(:focus-visible) { + outline: solid 1px transparent; +} + +.c2:focus-visible:not([disabled]) { + outline: 2px solid; + outline-color: #0969da; + outline-offset: -2px; } .c2:active:not([disabled]) { @@ -1845,10 +1861,6 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` box-shadow: none; } -.c3:focus { - outline: none; -} - .c3:disabled { cursor: default; color: #8c959f; @@ -1887,7 +1899,19 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` } .c3:focus:not([disabled]) { - box-shadow: 0 0 0 3px rgba(9,105,218,0.3); + outline: 2px solid; + outline-color: #0969da; + outline-offset: -2px; +} + +.c3:focus:not([disabled]):not(:focus-visible) { + outline: solid 1px transparent; +} + +.c3:focus-visible:not([disabled]) { + outline: 2px solid; + outline-color: #0969da; + outline-offset: -2px; } .c3:active:not([disabled]) {