diff --git a/.github/workflows/next_major.yml b/.github/workflows/next_major.yml
new file mode 100644
index 0000000000..7270404858
--- /dev/null
+++ b/.github/workflows/next_major.yml
@@ -0,0 +1,14 @@
+name: Next Major Pull Request
+on:
+ push:
+ branches:
+ - 'changeset-release/next_major'
+
+jobs:
+ next_major_pr:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Don't Merge
+ run: |
+ echo "Don't merge the next_major, changeset pr into next_major. Instead when you're ready to release a new major version, change the base of this pr to main, and merge."
+ exit 1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68c971b9a4..e62b656afe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
# @primer/css
+## 20.0.0
+
+### Major Changes
+
+- [#2049](https://github.com/primer/css/pull/2049) [`f4dba96e`](https://github.com/primer/css/commit/f4dba96e0cb78d3d451226cf60b01187678ced45) Thanks [@langermank](https://github.com/langermank)! - Comment box upload focus border-radius
+
+* [#1744](https://github.com/primer/css/pull/1744) [`942f65a4`](https://github.com/primer/css/commit/942f65a45a18b7042ba1ce2703688b62d874cf18) Thanks [@langermank](https://github.com/langermank)! - Global CSS focus styles
+
+- [#1767](https://github.com/primer/css/pull/1767) [`7e01db97`](https://github.com/primer/css/commit/7e01db97f3f2b9d9383f66385ca16f0bdc06abd1) Thanks [@tobiasahlin](https://github.com/tobiasahlin)! - Marketing: Remove unused pullquote selector
+
+* [#1821](https://github.com/primer/css/pull/1821) [`daa2685c`](https://github.com/primer/css/commit/daa2685c596d894b3bae1896bf97c3319cd9816c) Thanks [@jonrohan](https://github.com/jonrohan)! - UnderlineNav `:focus` styles
+ Refactor selected state and spacing
+ Add selected bold state override from github/github
+
+- [#2047](https://github.com/primer/css/pull/2047) [`553d72cc`](https://github.com/primer/css/commit/553d72cc1baaf43a4c743c50cc8881f3811123e9) Thanks [@langermank](https://github.com/langermank)! - UnderlineNav bug fix
+
+* [#2046](https://github.com/primer/css/pull/2046) [`55e2b069`](https://github.com/primer/css/commit/55e2b069a4cbd225af676bef918bbbe2e6cd73b7) Thanks [@langermank](https://github.com/langermank)! - Global focus style CSS from feature flag (next major)
+
+### Patch Changes
+
+- [#2048](https://github.com/primer/css/pull/2048) [`dc529e31`](https://github.com/primer/css/commit/dc529e3102788d5caa136a9d30c58e56721427a7) Thanks [@simurai](https://github.com/simurai)! - Fix headings with an anchor in a summary
+
+* [#2041](https://github.com/primer/css/pull/2041) [`20550bbf`](https://github.com/primer/css/commit/20550bbfaa236ac197ca58805542eaab0bf38fd4) Thanks [@jonrohan](https://github.com/jonrohan)! - Upgrading to stylelint-config-12.4.0
+
## 19.8.2
### Patch Changes
diff --git a/docs/src/stories/components/Forms/FormGroups.stories.jsx b/docs/src/stories/components/Forms/FormGroup.stories.jsx
similarity index 100%
rename from docs/src/stories/components/Forms/FormGroups.stories.jsx
rename to docs/src/stories/components/Forms/FormGroup.stories.jsx
diff --git a/docs/src/stories/components/Link/Link.stories.jsx b/docs/src/stories/components/Link/Link.stories.jsx
index b153ae71dd..a4a43380f6 100644
--- a/docs/src/stories/components/Link/Link.stories.jsx
+++ b/docs/src/stories/components/Link/Link.stories.jsx
@@ -71,7 +71,7 @@ export const LinkTemplate = ({label, variant, href, noUnderline, focusElement, f
<>
{label}
diff --git a/docs/src/stories/components/Marketing/MarketingButton.stories.jsx b/docs/src/stories/components/Marketing/MarketingButton.stories.jsx
index 69ef74016b..d523944fc7 100644
--- a/docs/src/stories/components/Marketing/MarketingButton.stories.jsx
+++ b/docs/src/stories/components/Marketing/MarketingButton.stories.jsx
@@ -7,7 +7,7 @@ export default {
layout: 'padded'
},
- excludeStories: ['ButtonTemplate'],
+ excludeStories: ['MarketingButtonTemplate'],
argTypes: {
variant: {
options: [0, 1, 2, 3], // iterator
@@ -77,7 +77,7 @@ const focusMethod = function getFocus() {
button.focus()
}
-export const ButtonTemplate = ({label, variant, disabled, size, animated, focusElement, focusAllElements}) => (
+export const MarketingButtonTemplate = ({label, variant, disabled, size, animated, focusElement, focusAllElements}) => (
<>
)
-export const Playground = ButtonTemplate.bind({})
+export const Playground = MarketingButtonTemplate.bind({})
Playground.args = {
animated: false,
focusElement: false,
diff --git a/docs/src/stories/components/Marketing/MarketingLink.stories.jsx b/docs/src/stories/components/Marketing/MarketingLink.stories.jsx
index 028b067f09..5acd98b70f 100644
--- a/docs/src/stories/components/Marketing/MarketingLink.stories.jsx
+++ b/docs/src/stories/components/Marketing/MarketingLink.stories.jsx
@@ -7,7 +7,7 @@ export default {
layout: 'padded'
},
- excludeStories: ['LinkTemplate'],
+ excludeStories: ['MarketingLinkTemplate'],
argTypes: {
size: {
options: [0, 1], // iterator
@@ -67,7 +67,7 @@ const focusMethod = function getFocus() {
link.focus()
}
-export const LinkTemplate = ({label, emphasis, href, size, focusElement, focusAllElements}) => (
+export const MarketingLinkTemplate = ({label, emphasis, href, size, focusElement, focusAllElements}) => (
<>
)
-export const Playground = LinkTemplate.bind({})
+export const Playground = MarketingLinkTemplate.bind({})
Playground.args = {
label: 'Link label',
href: '/',
diff --git a/docs/src/stories/components/Navigation/UnderlineNav.stories.jsx b/docs/src/stories/components/Navigation/UnderlineNav.stories.jsx
new file mode 100644
index 0000000000..a1e5a76dcb
--- /dev/null
+++ b/docs/src/stories/components/Navigation/UnderlineNav.stories.jsx
@@ -0,0 +1,73 @@
+import React from 'react'
+import clsx from 'clsx'
+import {UnderlineNavItemTemplate} from './UnderlineNavItem.stories'
+
+export default {
+ title: 'Components/Navigation/UnderlineNav',
+ excludeStories: ['UnderlineNavTemplate'],
+ layout: 'padded',
+ argTypes: {
+ variant: {
+ options: [0, 1, 2], // iterator
+ mapping: ['', 'UnderlineNav--right', 'UnderlineNav--full'], // values
+ control: {
+ type: 'inline-radio',
+ labels: ['default', 'align-right', 'fullwidth']
+ },
+ description: 'nav positioning options',
+ table: {
+ category: 'CSS'
+ }
+ },
+ children: {
+ description: 'creates a slot for children',
+ table: {
+ category: 'HTML'
+ }
+ },
+ actionStart: {
+ description: 'action to left of nav',
+ table: {
+ category: 'HTML'
+ }
+ },
+ actionEnd: {
+ description: 'action to right of nav',
+ table: {
+ category: 'HTML'
+ }
+ }
+ }
+}
+
+export const UnderlineNavTemplate = ({variant, children, actionStart, actionEnd}) => (
+ <>
+
+ >
+)
+
+export const Playground = UnderlineNavTemplate.bind({})
+Playground.args = {
+ variant: 0,
+ children: (
+ <>
+
+
+
+ >
+ )
+}
diff --git a/docs/src/stories/components/Navigation/UnderlineNavAction.stories.jsx b/docs/src/stories/components/Navigation/UnderlineNavAction.stories.jsx
new file mode 100644
index 0000000000..7e43dd03f8
--- /dev/null
+++ b/docs/src/stories/components/Navigation/UnderlineNavAction.stories.jsx
@@ -0,0 +1,56 @@
+import React from 'react'
+import clsx from 'clsx'
+import {ButtonTemplate} from '../Button/Button.stories'
+import {LinkTemplate} from '../Link/Link.stories'
+
+export default {
+ title: 'Components/Navigation/UnderlineNav/Action',
+ excludeStories: ['UnderlineNavActionTemplate'],
+ layout: 'padded',
+ argTypes: {
+ semanticItemType: {
+ options: ['button', 'link'],
+ control: {
+ type: 'inline-radio'
+ },
+ description: 'item can be a button or a link',
+ table: {
+ category: 'HTML'
+ }
+ },
+ label: {
+ name: 'label',
+ type: 'string',
+ description: 'Item label text',
+ table: {
+ category: 'HTML'
+ }
+ },
+ focusElement: {
+ control: {type: 'boolean'},
+ description: 'set manual focus on item',
+ table: {
+ category: 'Interactive'
+ }
+ }
+ }
+}
+
+export const UnderlineNavActionTemplate = ({semanticItemType, label, focusElement}) => {
+ return (
+
+ {semanticItemType === 'button' ? (
+
+ ) : (
+
+ )}
+
+ )
+}
+
+export const Playground = UnderlineNavActionTemplate.bind({})
+Playground.args = {
+ semanticItemType: 'button',
+ label: 'Action',
+ focusElement: false
+}
diff --git a/docs/src/stories/components/Navigation/UnderlineNavItem.stories.jsx b/docs/src/stories/components/Navigation/UnderlineNavItem.stories.jsx
new file mode 100644
index 0000000000..da27a1227f
--- /dev/null
+++ b/docs/src/stories/components/Navigation/UnderlineNavItem.stories.jsx
@@ -0,0 +1,140 @@
+import React from 'react'
+import clsx from 'clsx'
+import useToggle from '../../helpers/useToggle.jsx'
+
+export default {
+ title: 'Components/Navigation/UnderlineNav/Item',
+ excludeStories: ['UnderlineNavItemTemplate'],
+ layout: 'padded',
+ argTypes: {
+ selected: {
+ control: {type: 'boolean'},
+ description: 'active nav item',
+ table: {
+ category: 'CSS'
+ }
+ },
+ usesDataContent: {
+ control: {type: 'boolean'},
+ description: 'creates a hidden label to allow for bold text without layout shift',
+ table: {
+ category: 'CSS'
+ }
+ },
+ semanticItemType: {
+ options: ['button', 'link'],
+ control: {
+ type: 'inline-radio'
+ },
+ description: 'item can be a button or a link',
+ table: {
+ category: 'HTML'
+ }
+ },
+ label: {
+ name: 'label',
+ type: 'string',
+ description: 'Item label text',
+ table: {
+ category: 'HTML'
+ }
+ },
+ focusElement: {
+ control: {type: 'boolean'},
+ description: 'set manual focus on tab item',
+ table: {
+ category: 'Interactive'
+ }
+ },
+ icon: {
+ control: {type: 'boolean'},
+ description: 'show icon',
+ table: {
+ category: 'CSS'
+ }
+ },
+ counter: {
+ control: {type: 'boolean'},
+ description: 'show counter',
+ table: {
+ category: 'CSS'
+ }
+ }
+ }
+}
+
+export const UnderlineNavItemTemplate = ({
+ semanticItemType,
+ label,
+ selected,
+ focusElement,
+ icon,
+ counter,
+ usesDataContent
+}) => {
+ const [isSelected, itemisSelected] = useToggle()
+ return (
+
+ {semanticItemType === 'button' ? (
+
+ ) : (
+
+ {icon && (
+
+ )}
+ {label}
+ {counter && 10}
+
+ )}
+
+ )
+}
+
+export const Playground = UnderlineNavItemTemplate.bind({})
+Playground.args = {
+ semanticItemType: 'button',
+ label: 'Item',
+ selected: false,
+ focusElement: false,
+ icon: false,
+ counter: false,
+ usesDataContent: true
+}
diff --git a/docs/src/stories/components/Navigation/UnderlineNavPatterns.stories.jsx b/docs/src/stories/components/Navigation/UnderlineNavPatterns.stories.jsx
new file mode 100644
index 0000000000..f5b152b6b0
--- /dev/null
+++ b/docs/src/stories/components/Navigation/UnderlineNavPatterns.stories.jsx
@@ -0,0 +1,111 @@
+import React from 'react'
+import clsx from 'clsx'
+import {UnderlineNavTemplate} from './UnderlineNav.stories'
+import {UnderlineNavItemTemplate} from './UnderlineNavItem.stories'
+import {UnderlineNavActionTemplate} from './UnderlineNavAction.stories'
+
+export default {
+ title: 'Components/Navigation/UnderlineNav/Features',
+ layout: 'padded'
+}
+
+export const LinkItems = UnderlineNavTemplate.bind({})
+LinkItems.args = {
+ children: (
+ <>
+
+
+
+ >
+ )
+}
+
+export const ButtonItems = UnderlineNavTemplate.bind({})
+ButtonItems.args = {
+ children: (
+ <>
+
+
+
+ >
+ )
+}
+
+export const NavRight = UnderlineNavTemplate.bind({})
+NavRight.args = {
+ variant: 'UnderlineNav--right',
+ children: (
+ <>
+
+
+
+ >
+ )
+}
+
+export const NavFullWidth = UnderlineNavTemplate.bind({})
+NavFullWidth.args = {
+ variant: 'UnderlineNav--full',
+ children: (
+ <>
+
+
+
+ >
+ )
+}
+
+export const ActionRight = UnderlineNavTemplate.bind({})
+ActionRight.args = {
+ children: (
+ <>
+
+
+
+ >
+ ),
+ actionEnd:
+}
+
+export const ActionLeft = UnderlineNavTemplate.bind({})
+ActionLeft.args = {
+ children: (
+ <>
+
+
+
+ >
+ ),
+ actionStart:
+}
+
+export const Overflow = UnderlineNavTemplate.bind({})
+Overflow.args = {
+ children: (
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export const Icons = UnderlineNavTemplate.bind({})
+Icons.args = {
+ children: (
+ <>
+
+
+
+
+
+ >
+ )
+}
diff --git a/docs/src/stories/patterns/FocusStyles.stories.jsx b/docs/src/stories/patterns/FocusStyles.stories.jsx
new file mode 100644
index 0000000000..7b3dd2f747
--- /dev/null
+++ b/docs/src/stories/patterns/FocusStyles.stories.jsx
@@ -0,0 +1,88 @@
+import React from 'react'
+import clsx from 'clsx'
+import {ButtonTemplate} from '../components/Button/Button.stories.jsx'
+import {CheckboxTemplate} from '../components/Forms/Checkbox.stories.jsx'
+import {InputTemplate} from '../components/Forms/Input.stories.jsx'
+import {SelectTemplate} from '../components/Forms/Select.stories.jsx'
+import {TextareaTemplate} from '../components/Forms/Textarea.stories.jsx'
+import {LinkTemplate} from '../components/Link/Link.stories.jsx'
+import {MarketingButtonTemplate} from '../components/Marketing/MarketingButton.stories.jsx'
+import {MarketingLinkTemplate} from '../components/Marketing/MarketingLink.stories.jsx'
+import {TabNavTemplate} from '../components/Navigation/TabNav.stories.jsx'
+import {TabNavItemTemplate} from '../components/Navigation/TabNavItem.stories.jsx'
+
+export default {
+ title: 'Patterns/FocusStyles',
+ layout: 'padded'
+}
+
+export const FocusStyles = ({}) => (
+
+
+
+
+
+
+
+
+
+
+ `}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
:target styles
+
+
+)
diff --git a/package.json b/package.json
index e4c19ce299..b8bc4d0a24 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@primer/css",
- "version": "19.8.2",
+ "version": "20.0.0",
"description": "The CSS implementation of GitHub's Primer Design System",
"homepage": "https://primer.style/css",
"author": "GitHub, Inc.",
@@ -48,7 +48,7 @@
"@changesets/cli": "2.22.0",
"@github/prettier-config": "0.0.4",
"@koddsson/postcss-sass": "5.0.1",
- "@primer/stylelint-config": "^12.3.3",
+ "@primer/stylelint-config": "^12.4.0",
"autoprefixer": "10.4.4",
"chokidar-cli": "3.0.0",
"cssstats": "4.0.5",
diff --git a/src/actionlist/action-list-item.scss b/src/actionlist/action-list-item.scss
index f658277845..5f4ecb1a83 100644
--- a/src/actionlist/action-list-item.scss
+++ b/src/actionlist/action-list-item.scss
@@ -1,12 +1,5 @@
// stylelint-disable max-nesting-depth, selector-max-specificity, selector-max-compound-selectors
-@mixin focusOutline {
- position: relative;
- z-index: 1;
- outline: none;
- box-shadow: 0 0 0 2px var(--color-accent-fg); // this color breaks convention
-}
-
@mixin activeIndicatorLine {
position: absolute;
top: calc(50% - 12px);
@@ -314,6 +307,16 @@
text-decoration: none;
}
+ &:focus {
+ @include focusOutline;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ }
+ }
+
+ // default focus state
&:focus-visible {
@include focusOutline;
}
diff --git a/src/autocomplete/autocomplete.scss b/src/autocomplete/autocomplete.scss
index f0bf279e25..9d403f3fbb 100644
--- a/src/autocomplete/autocomplete.scss
+++ b/src/autocomplete/autocomplete.scss
@@ -31,9 +31,9 @@
align-items: center;
&:focus-within {
- border-color: var(--color-accent-emphasis);
- outline: none;
- box-shadow: var(--color-primer-shadow-focus);
+ border-color: var(--color-accent-fg);
+
+ @include focusBoxShadowInset;
}
.form-control {
@@ -46,6 +46,10 @@
&:focus {
box-shadow: none;
}
+
+ &:focus-visible {
+ box-shadow: none;
+ }
}
}
diff --git a/src/base/base.scss b/src/base/base.scss
index c0772e871c..7778f4b5d3 100644
--- a/src/base/base.scss
+++ b/src/base/base.scss
@@ -1,4 +1,4 @@
-// stylelint-disable selector-max-type
+// stylelint-disable selector-max-type, selector-no-qualifying-type
* {
box-sizing: border-box;
}
@@ -77,10 +77,70 @@ button {
}
details {
- summary { cursor: pointer; }
+ summary {
+ cursor: pointer;
+ }
&:not([open]) {
// Set details content hidden by default for browsers that don't do this
- > *:not(summary) { display: none !important; }
+ > *:not(summary) {
+ display: none !important;
+ }
+ }
+}
+
+// global focus styles
+
+a,
+button,
+[role='button'],
+input[type='radio'],
+input[type='checkbox'] {
+ transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
+ transition-property: color, background-color, box-shadow, border-color;
+ // fallback :focus state
+ &:focus {
+ @include focusOutline;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusOutline;
+ }
+}
+
+a:not([class]),
+input[type='radio'],
+input[type='checkbox'] {
+ &:focus,
+ &:focus-visible {
+ outline-offset: 0;
+ }
+}
+
+// for handling focus conditionally
+.focus {
+ @include focusBoxShadowInset;
+}
+
+// Windows High Contrast mode
+@media (forced-colors: active) {
+ *:focus,
+ *:focus-visible {
+ outline: solid 1px transparent;
+ }
+
+ input:not([type='radio'], [type='checkbox']),
+ textarea,
+ select {
+ &:focus,
+ &:focus-visible {
+ outline-offset: 2px;
+ }
}
}
diff --git a/src/base/normalize.scss b/src/base/normalize.scss
index 70222f260d..193a138a3d 100644
--- a/src/base/normalize.scss
+++ b/src/base/normalize.scss
@@ -39,7 +39,8 @@ header,
main, /* 2 */
menu,
nav,
-section { /* 1 */
+section {
+ /* 1 */
display: block;
}
@@ -96,16 +97,6 @@ a {
background-color: transparent; /* 1 */
}
-/**
- * Remove the outline on focused links when they are also active or hovered
- * in all browsers (opinionated).
- */
-
-a:active,
-a:hover {
- outline-width: 0;
-}
-
/* Text-level semantics
========================================================================== */
@@ -278,7 +269,8 @@ optgroup {
*/
button,
-input { /* 1 */
+input {
+ /* 1 */
overflow: visible;
}
@@ -288,7 +280,8 @@ input { /* 1 */
*/
button,
-select { /* 1 */
+select {
+ /* 1 */
text-transform: none;
}
@@ -305,29 +298,6 @@ html [type="button"], /* 1 */
-webkit-appearance: button; /* 2 */
}
-/**
- * Remove the inner border and padding in Firefox.
- */
-
-button::-moz-focus-inner,
-[type="button"]::-moz-focus-inner,
-[type="reset"]::-moz-focus-inner,
-[type="submit"]::-moz-focus-inner {
- border-style: none;
- padding: 0;
-}
-
-/**
- * Restore the focus styles unset by the previous rule.
- */
-
-button:-moz-focusring,
-[type="button"]:-moz-focusring,
-[type="reset"]:-moz-focusring,
-[type="submit"]:-moz-focusring {
- outline: 1px dotted ButtonText;
-}
-
/**
* Change the border, margin, and padding in all browsers (opinionated).
*/
@@ -367,8 +337,8 @@ textarea {
* 2. Remove the padding in IE 10-.
*/
-[type="checkbox"],
-[type="radio"] {
+[type='checkbox'],
+[type='radio'] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
@@ -377,27 +347,17 @@ textarea {
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
-[type="number"]::-webkit-inner-spin-button,
-[type="number"]::-webkit-outer-spin-button {
+[type='number']::-webkit-inner-spin-button,
+[type='number']::-webkit-outer-spin-button {
height: auto;
}
-/**
- * 1. Correct the odd appearance in Chrome and Safari.
- * 2. Correct the outline style in Safari.
- */
-
-[type="search"] {
- -webkit-appearance: textfield; /* 1 */
- outline-offset: -2px; /* 2 */
-}
-
/**
* Remove the inner padding and cancel buttons in Chrome and Safari on OS X.
*/
-[type="search"]::-webkit-search-cancel-button,
-[type="search"]::-webkit-search-decoration {
+[type='search']::-webkit-search-cancel-button,
+[type='search']::-webkit-search-decoration {
-webkit-appearance: none;
}
diff --git a/src/box/box-overlay.scss b/src/box/box-overlay.scss
index aa26b51e91..ed8f273364 100644
--- a/src/box/box-overlay.scss
+++ b/src/box/box-overlay.scss
@@ -1,4 +1,5 @@
.Box--overlay {
+ // stylelint-disable-next-line primer/responsive-widths
width: 448px;
margin-right: auto;
margin-left: auto;
@@ -22,6 +23,7 @@
}
.Box-overlay--wide {
+ // stylelint-disable-next-line primer/responsive-widths
width: 640px;
}
diff --git a/src/buttons/button.scss b/src/buttons/button.scss
index 316b54caf2..27563db247 100644
--- a/src/buttons/button.scss
+++ b/src/buttons/button.scss
@@ -66,8 +66,8 @@
background-color: var(--color-btn-bg);
border-color: var(--color-btn-border);
box-shadow: var(--color-btn-shadow), var(--color-btn-inset-shadow);
- transition: 0.2s cubic-bezier(0.3, 0, 0.5, 1);
- transition-property: color, background-color, border-color;
+ transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
+ transition-property: color, background-color, box-shadow, border-color;
&:hover,
&.hover,
@@ -100,14 +100,6 @@
color: var(--color-primer-fg-disabled);
}
}
-
- // Keep :focus after :disabled. Allows to see the focus ring even on disabled buttons
- &:focus,
- &.focus {
- border-color: var(--color-btn-focus-border);
- outline: none;
- box-shadow: var(--color-btn-focus-shadow);
- }
}
// Primary button
@@ -125,6 +117,22 @@
border-color: var(--color-btn-primary-hover-border);
}
+ // fallback :focus state
+ &:focus {
+ @include focusOutlineOnEmphasis;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ box-shadow: none;
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusOutlineOnEmphasis;
+ }
+
&:active,
&.selected,
&[aria-selected='true'] {
@@ -144,13 +152,6 @@
}
}
- &:focus,
- &.focus {
- background-color: var(--color-btn-primary-focus-bg);
- border-color: var(--color-btn-primary-focus-border);
- box-shadow: var(--color-btn-primary-focus-shadow);
- }
-
.Counter {
color: inherit;
background-color: var(--color-btn-primary-counter-bg);
@@ -161,6 +162,26 @@
}
}
+// ensure links styled as button primary gets proper focus style
+// stylelint-disable-next-line selector-no-qualifying-type
+a.btn-primary {
+ // fallback :focus state
+ &:focus {
+ @include focusOutlineOnEmphasis;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ box-shadow: none;
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusOutlineOnEmphasis;
+ }
+}
+
// Outline button
.btn-outline {
@@ -189,6 +210,22 @@
background-color: var(--color-btn-outline-selected-bg);
border-color: var(--color-btn-outline-selected-border);
box-shadow: var(--color-btn-outline-selected-shadow);
+
+ // fallback :focus state
+ &:focus {
+ @include focusOutlineOnEmphasis;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ box-shadow: none;
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusOutlineOnEmphasis;
+ }
}
&:disabled,
@@ -204,11 +241,6 @@
}
}
- &:focus {
- border-color: var(--color-btn-outline-focus-border);
- box-shadow: var(--color-btn-outline-focus-shadow);
- }
-
.Counter {
color: inherit;
background-color: var(--color-btn-outline-counter-bg);
@@ -266,11 +298,6 @@
}
}
- &:focus {
- border-color: var(--color-btn-danger-focus-border);
- box-shadow: var(--color-btn-danger-focus-shadow);
- }
-
.Counter {
color: inherit;
background-color: var(--color-btn-danger-counter-bg);
diff --git a/src/buttons/misc.scss b/src/buttons/misc.scss
index bc42d6751d..328b2fe27a 100644
--- a/src/buttons/misc.scss
+++ b/src/buttons/misc.scss
@@ -28,6 +28,14 @@
cursor: default;
}
}
+
+ &:not(.dropdown-item) {
+ &:focus,
+ &:focus-visible {
+ border-radius: $border-radius;
+ outline-offset: 0;
+ }
+ }
}
// Invisible button
@@ -37,6 +45,7 @@
color: var(--color-accent-fg);
background-color: transparent; // Reset default gradient backgrounds and colors
border: 0;
+ border-radius: $border-radius;
box-shadow: none;
&:hover,
@@ -48,15 +57,14 @@
}
&:active,
- &:focus,
&.selected,
&[aria-selected='true'],
&.zeroclipboard-is-active {
color: var(--color-accent-fg);
- background-color: none;
+ background: none;
border-color: var(--color-btn-active-border);
- outline: none;
- box-shadow: var(--color-btn-focus-shadow);
+
+ @include focusOutline;
}
&:active &.zeroclipboard-is-active {
@@ -89,14 +97,23 @@
border: 0;
box-shadow: none;
- &:hover { color: var(--color-accent-fg); }
+ &:hover {
+ color: var(--color-accent-fg);
+ }
+
+ &:focus,
+ &:focus-visible {
+ border-radius: $border-radius;
+ }
&.disabled,
&[aria-disabled='true'] {
color: var(--color-primer-fg-disabled);
cursor: default;
- &:hover { color: var(--color-primer-fg-disabled); }
+ &:hover {
+ color: var(--color-primer-fg-disabled);
+ }
}
}
@@ -112,18 +129,13 @@
color: var(--color-fg-muted);
background: transparent;
border: 0;
- outline: none;
&:hover {
color: var(--color-fg-default);
}
- &:active,
- &:focus {
- color: var(--color-fg-muted);
- border-color: var(--color-btn-active-border);
- outline: none;
- box-shadow: var(--color-btn-focus-shadow);
+ &:active {
+ @include focusOutline;
}
}
@@ -216,10 +228,4 @@
color: var(--color-accent-fg);
cursor: pointer;
}
-
- &:focus {
- z-index: 1;
- outline: 0;
- box-shadow: var(--color-primer-shadow-focus);
- }
}
diff --git a/src/dropdown/dropdown.scss b/src/dropdown/dropdown.scss
index 4789eccd0c..bf959e154f 100644
--- a/src/dropdown/dropdown.scss
+++ b/src/dropdown/dropdown.scss
@@ -82,12 +82,10 @@
text-overflow: ellipsis;
white-space: nowrap;
- &:focus,
&:hover {
color: var(--color-fg-on-emphasis);
text-decoration: none;
background-color: var(--color-accent-emphasis);
- outline: none;
> .octicon {
color: inherit;
diff --git a/src/forms/form-control.scss b/src/forms/form-control.scss
index 23dfc33f30..b8eb49990a 100644
--- a/src/forms/form-control.scss
+++ b/src/forms/form-control.scss
@@ -29,14 +29,32 @@ label {
background-position: right 8px center; // For form validation. This keeps images 8px from right and centered vertically.
border: $border-width $border-style var(--color-border-default);
border-radius: $border-radius;
- outline: none;
box-shadow: var(--color-primer-shadow-inset);
+ transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
+ transition-property: color, background-color, box-shadow, border-color;
- &.focus,
&:focus {
- border-color: var(--color-accent-emphasis);
- outline: none;
- box-shadow: var(--color-primer-shadow-focus);
+ @include focusBoxShadowInset;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ border-color: transparent;
+
+ @include focusBoxShadowInset(1px, transparent);
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusBoxShadowInset;
+ }
+
+ // override form controls with no border in focused state
+ &.border-0 {
+ &:focus,
+ &:focus-visible {
+ border: $border-width $border-style var(--color-accent-fg) !important;
+ }
}
&[disabled] {
diff --git a/src/forms/form-group.scss b/src/forms/form-group.scss
index 1ec0f8a278..188bfd6449 100644
--- a/src/forms/form-group.scss
+++ b/src/forms/form-group.scss
@@ -23,6 +23,7 @@
// Text fields
.form-control {
+ // stylelint-disable-next-line primer/responsive-widths
width: 440px;
max-width: 100%;
// stylelint-disable-next-line primer/spacing
@@ -33,12 +34,18 @@
background-color: var(--color-canvas-default);
}
- &.shorter { width: 130px; }
+ &.shorter {
+ width: 130px;
+ }
- &.short { width: 250px; }
+ &.short {
+ width: 250px;
+ }
&.input-block,
- &.long { width: 100%; }
+ &.long {
+ width: 100%;
+ }
}
// Textarea
@@ -89,9 +96,13 @@
h4 {
margin: $spacer-1 0 0;
- &.is-error { color: var(--color-danger-fg); }
+ &.is-error {
+ color: var(--color-danger-fg);
+ }
- &.is-success { color: var(--color-success-fg); }
+ &.is-success {
+ color: var(--color-success-fg);
+ }
+ .note {
margin-top: 0;
@@ -203,13 +214,18 @@
background-image: linear-gradient(var(--color-success-subtle), var(--color-success-subtle));
border-color: var(--color-success-muted);
- &::after { border-bottom-color: var(--color-success-subtle); }
- &::before { border-bottom-color: var(--color-success-muted); }
+ &::after {
+ border-bottom-color: var(--color-success-subtle);
+ }
+
+ &::before {
+ border-bottom-color: var(--color-success-muted);
+ }
}
}
&.warn {
- .form-control {
+ .form-control:not(:focus, :focus-visible) {
border-color: var(--color-attention-emphasis);
}
@@ -219,13 +235,18 @@
background-image: linear-gradient(var(--color-attention-subtle), var(--color-attention-subtle));
border-color: var(--color-attention-muted);
- &::after { border-bottom-color: var(--color-attention-subtle); }
- &::before { border-bottom-color: var(--color-attention-muted); }
+ &::after {
+ border-bottom-color: var(--color-attention-subtle);
+ }
+
+ &::before {
+ border-bottom-color: var(--color-attention-muted);
+ }
}
}
&.errored {
- .form-control {
+ .form-control:not(:focus, :focus-visible) {
border-color: var(--color-danger-emphasis);
}
@@ -239,8 +260,13 @@
background-image: linear-gradient(var(--color-danger-subtle), var(--color-danger-subtle));
border-color: var(--color-danger-muted);
- &::after { border-bottom-color: var(--color-danger-subtle); }
- &::before { border-bottom-color: var(--color-danger-muted); }
+ &::after {
+ border-bottom-color: var(--color-danger-subtle);
+ }
+
+ &::before {
+ border-bottom-color: var(--color-danger-muted);
+ }
}
}
}
diff --git a/src/forms/form-validation.scss b/src/forms/form-validation.scss
index bb96b73a1f..e0c27b94ba 100644
--- a/src/forms/form-validation.scss
+++ b/src/forms/form-validation.scss
@@ -235,15 +235,20 @@ dl.form-group > dd, // TODO: Deprecate
}
&.focused {
+ border-color: var(--color-accent-fg);
border-radius: $border-radius;
- box-shadow: var(--color-primer-shadow-inset), var(--color-primer-shadow-focus);
+ outline: none;
+ // stylelint-disable-next-line primer/box-shadow
+ box-shadow: 0 0 0 2px var(--color-accent-fg);
.form-control {
+ border-color: transparent;
+ border-bottom-color: var(--color-accent-fg);
box-shadow: none;
}
.drag-and-drop {
- border-color: var(--color-accent-emphasis);
+ border-color: transparent;
}
}
}
@@ -272,7 +277,9 @@ dl.form-group > dd, // TODO: Deprecate
border: $border-width $border-style var(--color-border-default);
}
- .comment-form-error { margin-bottom: $spacer-2; }
+ .comment-form-error {
+ margin-bottom: $spacer-2;
+ }
.write-content,
.preview-content {
@@ -317,8 +324,10 @@ div.composer {
min-height: 200px;
}
-// stylelint-disable-next-line primer/spacing
-.composer .tabnav { margin: 0 0 10px; }
+.composer .tabnav {
+ // stylelint-disable-next-line primer/spacing
+ margin: 0 0 10px;
+}
// Misc CSS
//
diff --git a/src/forms/input-group.scss b/src/forms/input-group.scss
index 1186c4694a..01534cb5a4 100644
--- a/src/forms/input-group.scss
+++ b/src/forms/input-group.scss
@@ -19,6 +19,14 @@
display: inline-table;
}
+ // within input group, if button exists change focus styles to match input (no offset)
+ &:focus-within {
+ // stylelint-disable-next-line selector-max-type
+ button {
+ outline-offset: 0;
+ }
+ }
+
// Autocomplete with embedded icon
.form-control.autocomplete-embedded-icon-wrap {
display: inline-flex;
diff --git a/src/links/link.scss b/src/links/link.scss
index 80cad058e7..f29cef3d91 100644
--- a/src/links/link.scss
+++ b/src/links/link.scss
@@ -7,6 +7,11 @@
text-decoration: underline;
cursor: pointer;
}
+
+ &:focus,
+ &:focus-visible {
+ outline-offset: 0;
+ }
}
.Link--primary {
diff --git a/src/markdown/headings.scss b/src/markdown/headings.scss
index b9d420ebed..03a1311779 100644
--- a/src/markdown/headings.scss
+++ b/src/markdown/headings.scss
@@ -80,6 +80,11 @@
h5,
h6 {
display: inline-block;
+
+ .anchor {
+ // stylelint-disable-next-line primer/spacing
+ margin-left: -40px;
+ }
}
h1,
diff --git a/src/marketing/buttons/button.scss b/src/marketing/buttons/button.scss
index 8638eee851..854b3ec31f 100644
--- a/src/marketing/buttons/button.scss
+++ b/src/marketing/buttons/button.scss
@@ -13,11 +13,13 @@
white-space: nowrap;
vertical-align: middle;
user-select: none;
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0) 100%), var(--color-mktg-btn-bg) !important;
+ background:
+ linear-gradient(180deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0) 100%),
+ var(--color-mktg-btn-bg) !important;
border: 0;
// stylelint-disable-next-line primer/borders
border-radius: 0.375rem;
- transition: box-shadow 0.2s;
+ transition: box-shadow 0.2s, outline 0.2s ease;
appearance: none !important;
&::before {
@@ -42,29 +44,32 @@
box-shadow: var(--color-mktg-btn-shadow-hover) !important;
}
- &:focus,
- &.focus {
- outline: 0;
- box-shadow: var(--color-mktg-btn-shadow-focus), var(--color-mktg-btn-shadow-hover) !important;
- }
-
&:hover,
&:focus,
+ &:focus-visible,
&.focus {
&::before {
opacity: 1;
}
+ }
+
+ // fallback :focus state
+ &:focus {
+ @include focusOutline(2px, var(--color-accent-fg));
- &:disabled {
- box-shadow: none !important;
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ box-shadow: none;
}
}
- &:active {
- outline: none;
- // stylelint-disable-next-line primer/box-shadow
- box-shadow: 0 0 0 transparent;
+ // default focus state
+ &:focus-visible {
+ @include focusOutline(2px, var(--color-accent-fg));
+ }
+ &:active {
&::before {
opacity: 0.5 !important;
}
@@ -91,11 +96,6 @@
box-shadow: var(--color-mktg-btn-shadow-hover-muted) !important;
}
- &:focus,
- &.focus {
- box-shadow: var(--color-mktg-btn-shadow-hover-muted), var(--color-mktg-btn-shadow-focus) !important;
- }
-
&:active {
// stylelint-disable-next-line primer/box-shadow
box-shadow: var(--color-fg-default) 0 0 0 3px inset !important;
@@ -119,11 +119,6 @@
&:hover {
box-shadow: var(--color-mktg-btn-shadow-hover-muted) !important;
}
-
- &:focus,
- .focus {
- box-shadow: var(--color-mktg-btn-shadow-hover-muted), var(--color-mktg-btn-shadow-focus) !important;
- }
}
.btn-signup-mktg {
@@ -137,9 +132,20 @@
background: linear-gradient(180deg, rgba(52, 183, 89, 0.15) 0%, rgba(46, 164, 79, 0) 100%) !important;
}
+ // fallback :focus state
&:focus {
- // stylelint-disable-next-line primer/box-shadow
- box-shadow: rgba(46, 164, 79, 0.45) 0 0 0 4px !important;
+ @include focusOutline(2px, var(--color-accent-fg));
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ box-shadow: none;
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusOutline(2px, var(--color-accent-fg));
}
}
diff --git a/src/marketing/links/link.scss b/src/marketing/links/link.scss
index 13dd30aadc..9e87d28ebe 100644
--- a/src/marketing/links/link.scss
+++ b/src/marketing/links/link.scss
@@ -6,10 +6,6 @@
text-decoration: none;
}
- &:active {
- outline: none;
- }
-
&::after,
&.link-emphasis-mktg::before {
position: absolute;
@@ -35,13 +31,17 @@
}
&:hover,
- &:focus,
&:active {
&::after {
transform: scaleX(1);
}
}
+ &:focus,
+ &:focus-visible {
+ outline-offset: 2px;
+ }
+
&.arrow-target-mktg {
.arrow-symbol-mktg {
margin-left: -$em-spacer-3;
diff --git a/src/marketing/type/typography.scss b/src/marketing/type/typography.scss
index 94dde0d7a2..1ec7764c23 100644
--- a/src/marketing/type/typography.scss
+++ b/src/marketing/type/typography.scss
@@ -15,18 +15,25 @@
@each $header, $sizes in $mktg-headers {
.h#{$header}-mktg {
+ // stylelint-disable-next-line function-no-unknown
$pairing: map-get($mktg-header-pairings, nth($sizes, 1));
+ // stylelint-disable-next-line function-no-unknown
$pairing-md: map-get($mktg-header-pairings, nth($sizes, 2));
+ // stylelint-disable-next-line function-no-unknown
$pairing-lg: map-get($mktg-header-pairings, nth($sizes, 3));
+ // stylelint-disable-next-line function-no-unknown
font-size: map-get($pairing, 'size') !important;
+ // stylelint-disable-next-line function-no-unknown
line-height: map-get($pairing, 'lh') !important;
@if (map-get($pairing, 'size') >= $mktg-header-weight-threshold) { font-weight: $mktg-header-weight-large !important; }
@if (nth($sizes, 1) != nth($sizes, 2)) {
@include breakpoint(md) {
+ // stylelint-disable-next-line function-no-unknown
font-size: map-get($pairing-md, 'size') !important;
+ // stylelint-disable-next-line function-no-unknown
line-height: map-get($pairing-md, 'lh') !important;
@if (map-get($pairing-md, 'size') >= $mktg-header-spacing-threshold and map-get($pairing, 'size') < $mktg-header-spacing-threshold) {
@@ -41,7 +48,9 @@
@if (nth($sizes, 2) != nth($sizes, 3)) {
@include breakpoint(lg) {
+ // stylelint-disable-next-line function-no-unknown
font-size: map-get($pairing-lg, 'size') !important;
+ // stylelint-disable-next-line function-no-unknown
line-height: map-get($pairing-lg, 'lh') !important;
@if (map-get($pairing-lg, 'size') >= $mktg-header-spacing-threshold and map-get($pairing-md, 'size') < $mktg-header-spacing-threshold) {
@@ -70,11 +79,16 @@
@each $body, $sizes in $mktg-bodies {
.f#{$body}-mktg {
+ // stylelint-disable-next-line function-no-unknown
$pairing: map-get($mktg-body-pairings, nth($sizes, 1));
+ // stylelint-disable-next-line function-no-unknown
$pairing-md: map-get($mktg-body-pairings, nth($sizes, 2));
+ // stylelint-disable-next-line function-no-unknown
$pairing-lg: map-get($mktg-body-pairings, nth($sizes, 3));
+ // stylelint-disable-next-line function-no-unknown
font-size: map-get($pairing, 'size') !important;
+ // stylelint-disable-next-line function-no-unknown
line-height: map-get($pairing, 'lh') !important;
@if (map-get($pairing, 'size') >= $mktg-body-spacing-threshold) { letter-spacing: $mktg-body-spacing-large !important; }
@@ -83,7 +97,9 @@
@if (nth($sizes, 1) != nth($sizes, 2)) {
@include breakpoint(md) {
+ // stylelint-disable-next-line function-no-unknown
font-size: map-get($pairing-md, 'size') !important;
+ // stylelint-disable-next-line function-no-unknown
line-height: map-get($pairing-md, 'lh') !important;
@if (map-get($pairing-md, 'size') >= $mktg-body-spacing-threshold and map-get($pairing, 'size') < $mktg-body-spacing-threshold) {
@@ -98,7 +114,9 @@
@if (nth($sizes, 2) != nth($sizes, 3)) {
@include breakpoint(lg) {
+ // stylelint-disable-next-line function-no-unknown
font-size: map-get($pairing-lg, 'size') !important;
+ // stylelint-disable-next-line function-no-unknown
line-height: map-get($pairing-lg, 'lh') !important;
@if (map-get($pairing-lg, 'size') >= $mktg-body-spacing-threshold and map-get($pairing-md, 'size') < $mktg-body-spacing-threshold) {
@@ -116,32 +134,3 @@
.text-medium {
font-weight: $font-weight-medium !important;
}
-
-// Pullquote
-
-@mixin pullquote {
- padding-top: 0;
- padding-bottom: 0;
- // stylelint-disable-next-line primer/spacing
- padding-left: $spacer;
- margin-bottom: $spacer-4;
- font-family: $mono-font;
- font-size: $h4-size;
- line-height: 1.4;
- color: var(--color-color-text-secondary);
- border-left: 3px solid var(--color-border-default);
-
- @include breakpoint(md) {
- // stylelint-disable-next-line primer/spacing
- padding-left: $spacer * 1.5;
- margin-bottom: $spacer-5;
- // stylelint-disable-next-line primer/spacing
- margin-left: (-$spacer * 1.5) - 3px;
- font-size: 18px;
- line-height: $lh-default;
- }
-}
-
-.pullquote {
- @include pullquote;
-}
diff --git a/src/marketing/utilities/layout.scss b/src/marketing/utilities/layout.scss
index 6f464beb4d..94301c1647 100644
--- a/src/marketing/utilities/layout.scss
+++ b/src/marketing/utilities/layout.scss
@@ -27,7 +27,7 @@
@each $breakpoint, $variant in $responsive-variants {
@include breakpoint($breakpoint) {
@for $offset from 1 through 7 {
- // stylelint-disable-next-line primer/spacing
+ // stylelint-disable-next-line primer/spacing, function-no-unknown
.offset#{$variant}-n#{$offset} { margin-left: -($offset * 0.0833333333 * 100%); }
}
}
diff --git a/src/navigation/filter-list.scss b/src/navigation/filter-list.scss
index 68a317cbaa..9eac3d8aec 100644
--- a/src/navigation/filter-list.scss
+++ b/src/navigation/filter-list.scss
@@ -1,3 +1,5 @@
+// stylelint-disable selector-max-specificity
+
// Filters list
//
// A vertical list of filters.
@@ -45,6 +47,22 @@
&[aria-current]:not([aria-current='false']) {
color: var(--color-fg-on-emphasis);
background-color: var(--color-accent-emphasis);
+
+ // fallback :focus state
+ &:focus {
+ @include focusOutlineOnEmphasis;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ box-shadow: none;
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusOutlineOnEmphasis;
+ }
}
.count {
diff --git a/src/navigation/menu.scss b/src/navigation/menu.scss
index ef921593cd..0cfb57b622 100644
--- a/src/navigation/menu.scss
+++ b/src/navigation/menu.scss
@@ -22,7 +22,9 @@
border-top-left-radius: $border-radius;
border-top-right-radius: $border-radius;
- &::before { border-top-left-radius: $border-radius; }
+ &::before {
+ border-top-left-radius: $border-radius;
+ }
}
&:last-child {
@@ -30,13 +32,9 @@
border-bottom-right-radius: $border-radius;
border-bottom-left-radius: $border-radius;
- &::before { border-bottom-left-radius: $border-radius; }
- }
-
- &:focus {
- z-index: 1;
- outline: none;
- box-shadow: var(--color-primer-shadow-focus);
+ &::before {
+ border-bottom-left-radius: $border-radius;
+ }
}
&:hover {
diff --git a/src/navigation/sidenav.scss b/src/navigation/sidenav.scss
index 7fb8a90dd3..26d8d0a180 100644
--- a/src/navigation/sidenav.scss
+++ b/src/navigation/sidenav.scss
@@ -43,16 +43,9 @@
// States
-.SideNav-item:focus {
- z-index: 1;
- outline: none;
- box-shadow: var(--color-primer-shadow-focus);
-}
-
.SideNav-item:hover {
text-decoration: none;
background-color: var(--color-neutral-subtle);
- outline: none;
}
.SideNav-item:active {
@@ -94,11 +87,9 @@
border: 0;
}
-.SideNav-subItem:hover,
-.SideNav-subItem:focus {
+.SideNav-subItem:hover {
color: var(--color-fg-default);
text-decoration: none;
- outline: none;
}
.SideNav-subItem[aria-current]:not([aria-current='false']),
diff --git a/src/navigation/subnav.scss b/src/navigation/subnav.scss
index 89e03e8fa9..c973d64e35 100644
--- a/src/navigation/subnav.scss
+++ b/src/navigation/subnav.scss
@@ -1,3 +1,5 @@
+// stylelint-disable selector-max-specificity
+
// Needs refactoring
// Sub nav
.subnav {
@@ -46,6 +48,22 @@
color: var(--color-fg-on-emphasis);
background-color: var(--color-accent-emphasis);
border-color: var(--color-accent-emphasis);
+
+ // fallback :focus state
+ &:focus {
+ @include focusOutlineOnEmphasis;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ box-shadow: none;
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusOutlineOnEmphasis;
+ }
}
&:first-child {
@@ -72,6 +90,7 @@
}
.subnav-search-input-wide {
+ // stylelint-disable-next-line primer/responsive-widths
width: 500px;
}
diff --git a/src/navigation/tabnav.scss b/src/navigation/tabnav.scss
index 5d52cc5693..1df230af96 100644
--- a/src/navigation/tabnav.scss
+++ b/src/navigation/tabnav.scss
@@ -40,13 +40,18 @@
}
}
- &:hover,
- &:focus {
+ &:hover {
color: var(--color-fg-default);
text-decoration: none;
transition-duration: 0.1s;
}
+ &:focus,
+ &:focus-visible {
+ border-radius: $border-radius $border-radius 0 0 !important;
+ outline-offset: -6px;
+ }
+
&:active {
color: var(--color-fg-muted);
}
diff --git a/src/navigation/underline-nav.scss b/src/navigation/underline-nav.scss
index f188899df1..356fea8b70 100644
--- a/src/navigation/underline-nav.scss
+++ b/src/navigation/underline-nav.scss
@@ -1,50 +1,104 @@
+$nav-height: $spacer-3 * 3 !default; // 48px
+
.UnderlineNav {
display: flex;
+ min-height: $nav-height;
overflow-x: auto;
overflow-y: hidden;
// stylelint-disable-next-line primer/box-shadow
box-shadow: inset 0 -1px 0 var(--color-border-muted);
+ -webkit-overflow-scrolling: auto;
justify-content: space-between;
+
+ .Counter {
+ margin-left: $spacer-2;
+ color: var(--color-fg-default);
+ background-color: var(--color-neutral-muted);
+
+ &--primary {
+ color: var(--color-fg-on-emphasis);
+ background-color: var(--color-neutral-emphasis);
+ }
+ }
}
.UnderlineNav-body {
display: flex;
+ align-items: center;
+ gap: $spacer-2;
+ list-style: none;
}
.UnderlineNav-item {
- padding: $spacer-2 $spacer-3;
+ position: relative;
+ display: flex;
+ padding: 0 $spacer-2;
font-size: $body-font-size;
// stylelint-disable-next-line primer/typography
line-height: 30px;
color: var(--color-fg-default);
text-align: center;
white-space: nowrap;
+ cursor: pointer;
background-color: transparent;
border: 0;
- // stylelint-disable-next-line primer/borders
- border-bottom: 2px $border-style transparent;
+ border-radius: $border-radius;
+ align-items: center;
&:hover,
- &:focus {
+ &:focus,
+ &:focus-visible {
color: var(--color-fg-default);
text-decoration: none;
border-bottom-color: var(--color-neutral-muted);
- outline: 1px dotted transparent; // Support Firefox custom colors
- outline-offset: -1px;
+ outline-offset: -2px;
transition: border-bottom-color 0.12s ease-out;
}
+ // renders a visibly hidden "copy" of the label in bold, reserving box space for when label becomes bold on selected
+ [data-content]::before {
+ display: block;
+ height: 0;
+ font-weight: $font-weight-bold;
+ visibility: hidden;
+ content: attr(data-content);
+ }
+
+ // increase touch target area
+ &::before {
+ @include minTouchTarget($min-height: $nav-height);
+ }
+
+ // hover state was "sticking" on mobile after click
+ @media (pointer: fine) {
+ &:hover {
+ color: var(--color-fg-default);
+ text-decoration: none;
+ background: var(--color-action-list-item-default-hover-bg);
+ transition: background 0.12s ease-out;
+ }
+ }
+
&.selected,
&[role='tab'][aria-selected='true'],
&[aria-current]:not([aria-current='false']) {
font-weight: $font-weight-bold;
color: var(--color-fg-default);
border-bottom-color: var(--color-primer-border-active);
- outline: 1px dotted transparent; // Support Firefox custom colors
- outline-offset: -1px;
+ outline-offset: -8px;
- .UnderlineNav-octicon {
- color: var(--color-fg-muted);
+ // current/selected underline
+ &::after {
+ position: absolute;
+ right: 50%;
+ // 48px total height / 2 (24px) + 1px
+ bottom: calc(50% - 25px);
+ width: 100%;
+ height: 2px;
+ content: '';
+ background: var(--color-primer-border-active);
+ border-radius: $border-radius;
+ transform: translate(50%, -50%);
}
}
}
@@ -63,22 +117,18 @@
.UnderlineNav--full {
display: block;
-}
-.UnderlineNav-octicon {
- margin-right: $spacer-1;
- color: var(--color-fg-subtle);
+ // required for underline to align with additional wrapper element
+ .UnderlineNav-body {
+ min-height: $nav-height;
+ }
}
-.UnderlineNav .Counter {
- margin-left: $spacer-1;
- color: var(--color-fg-default);
- background-color: var(--color-neutral-muted);
-
- &--primary {
- color: var(--color-fg-on-emphasis);
- background-color: var(--color-neutral-emphasis);
- }
+.UnderlineNav-octicon {
+ display: inline !important;
+ margin-right: $spacer-2;
+ color: var(--color-fg-muted);
+ fill: var(--color-fg-muted);
}
.UnderlineNav-container {
diff --git a/src/pagination/pagination.scss b/src/pagination/pagination.scss
index 475ef0c044..43090b4e74 100644
--- a/src/pagination/pagination.scss
+++ b/src/pagination/pagination.scss
@@ -24,7 +24,6 @@
&:focus {
text-decoration: none;
border-color: var(--color-border-default);
- outline: 0;
transition-duration: 0.1s;
}
@@ -74,13 +73,35 @@
// chevron-left
.previous_page::before {
margin-right: $spacer-1;
- clip-path: polygon(9.8px 12.8px, 8.7px 12.8px, 4.5px 8.5px, 4.5px 7.5px, 8.7px 3.2px, 9.8px 4.3px, 6.1px 8px, 9.8px 11.7px, 9.8px 12.8px);
+ clip-path:
+ polygon(
+ 9.8px 12.8px,
+ 8.7px 12.8px,
+ 4.5px 8.5px,
+ 4.5px 7.5px,
+ 8.7px 3.2px,
+ 9.8px 4.3px,
+ 6.1px 8px,
+ 9.8px 11.7px,
+ 9.8px 12.8px
+ );
}
// chevron-right
.next_page::after {
margin-left: $spacer-1;
- clip-path: polygon(6.2px 3.2px, 7.3px 3.2px, 11.5px 7.5px, 11.5px 8.5px, 7.3px 12.8px, 6.2px 11.7px, 9.9px 8px, 6.2px 4.3px, 6.2px 3.2px);
+ clip-path:
+ polygon(
+ 6.2px 3.2px,
+ 7.3px 3.2px,
+ 11.5px 7.5px,
+ 11.5px 8.5px,
+ 7.3px 12.8px,
+ 6.2px 11.7px,
+ 9.9px 8px,
+ 6.2px 4.3px,
+ 6.2px 3.2px
+ );
}
}
diff --git a/src/popover/popover.scss b/src/popover/popover.scss
index 082a48fbb8..e283d9e9a7 100644
--- a/src/popover/popover.scss
+++ b/src/popover/popover.scss
@@ -122,6 +122,7 @@
}
&::before {
+ // stylelint-disable-next-line function-no-unknown
margin-top: -($spacer-2 + 1);
}
diff --git a/src/support/mixins/color-modes.scss b/src/support/mixins/color-modes.scss
index 6383c27931..3a5335d64c 100644
--- a/src/support/mixins/color-modes.scss
+++ b/src/support/mixins/color-modes.scss
@@ -84,10 +84,12 @@
@each $name, $value in $color-map {
@each $type, $color in $value {
@if $type == dark {
+ // stylelint-disable-next-line function-no-unknown
$dark-colors: append($dark-colors, (--color-#{$name}, #{$color}));
}
@else {
+ // stylelint-disable-next-line function-no-unknown
$light-colors: append($light-colors, (--color-#{$name}, #{$color}));
}
}
diff --git a/src/support/mixins/layout.scss b/src/support/mixins/layout.scss
index a3c51d705f..dfd0613d18 100644
--- a/src/support/mixins/layout.scss
+++ b/src/support/mixins/layout.scss
@@ -7,6 +7,7 @@
@else {
// Retrieves the value from the key
+ // stylelint-disable-next-line function-no-unknown
$value: map-get($breakpoints, $breakpoint);
// If the key exists in the map
diff --git a/src/support/mixins/misc.scss b/src/support/mixins/misc.scss
index d04208f9cf..90d4eeb1b9 100644
--- a/src/support/mixins/misc.scss
+++ b/src/support/mixins/misc.scss
@@ -16,7 +16,7 @@
&::after {
// stylelint-disable-next-line primer/spacing
- margin-left: 1px;
+ margin-left: 2px;
background-color: var(--color-canvas-default);
background-image: linear-gradient($background, $background);
}
@@ -25,3 +25,49 @@
background-color: $border;
}
}
+
+// global focus styles
+
+// inset box-shadow for form controls
+@mixin focusBoxShadowInset($outlineWidth: 1px, $outlineColor: var(--color-accent-fg)) {
+ border-color: var(--color-accent-fg);
+ outline: none;
+ box-shadow: inset 0 0 0 $outlineWidth $outlineColor;
+}
+
+// box-shadow for :target styles (no inset)
+// !important to override PCSS utilities
+@mixin targetBoxShadow($outlineWidth: 2px, $outlineColor: var(--color-accent-fg)) {
+ outline: none !important;
+ box-shadow: 0 0 0 $outlineWidth $outlineColor !important;
+}
+
+// outline
+@mixin focusOutline($outlineOffset: -2px, $outlineColor: var(--color-accent-fg)) {
+ outline: 2px solid $outlineColor;
+ outline-offset: $outlineOffset;
+ box-shadow: none;
+}
+
+// outline with fg box-shadow for buttons
+@mixin focusOutlineOnEmphasis($outlineOffset: -2px, $outlineColor: var(--color-accent-fg)) {
+ outline: 2px solid $outlineColor;
+ outline-offset: $outlineOffset;
+ box-shadow: inset 0 0 0 3px var(--color-fg-on-emphasis);
+}
+
+// if min-width is undefined, return only min-height
+@mixin minTouchTarget($min-height: 32px, $min-width: '') {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 100%;
+ height: 100%;
+ min-height: $min-height;
+ content: '';
+ transform: translateX(-50%) translateY(-50%);
+
+ @if $min-width != '' {
+ min-width: $min-width;
+ }
+}
diff --git a/src/support/variables/layout.scss b/src/support/variables/layout.scss
index 47dbbf72b8..2f99101725 100644
--- a/src/support/variables/layout.scss
+++ b/src/support/variables/layout.scss
@@ -82,6 +82,7 @@ $spacers-large: (
12: $spacer-12,
) !default;
+// stylelint-disable-next-line function-no-unknown
$spacer-map-extended: map-merge(
(
0: 0,
diff --git a/src/timeline/timeline-item.scss b/src/timeline/timeline-item.scss
index fcbf377c2a..18907bdc88 100644
--- a/src/timeline/timeline-item.scss
+++ b/src/timeline/timeline-item.scss
@@ -57,6 +57,7 @@
.TimelineItem-avatar {
position: absolute;
+ // stylelint-disable-next-line function-no-unknown
left: -($spacer-6 + $spacer-5);
z-index: 1;
}
@@ -67,6 +68,7 @@
height: $spacer-4;
margin: 0;
margin-bottom: -$spacer-3;
+ // stylelint-disable-next-line function-no-unknown
margin-left: -($spacer-6 + $spacer-3);
background-color: var(--color-canvas-default);
border: 0;
diff --git a/src/toasts/toasts.scss b/src/toasts/toasts.scss
index 3558a5b3ad..ed1cb21da9 100644
--- a/src/toasts/toasts.scss
+++ b/src/toasts/toasts.scss
@@ -40,9 +40,7 @@
background-color: transparent;
border: 0;
- &:focus,
&:hover {
- outline: none;
opacity: 0.7;
}
@@ -119,6 +117,11 @@
}
@keyframes Toast--spinner {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(360deg);
+ }
}
diff --git a/src/utilities/details.scss b/src/utilities/details.scss
index ad214f5654..df25180e46 100644
--- a/src/utilities/details.scss
+++ b/src/utilities/details.scss
@@ -1,4 +1,4 @@
-// stylelint-disable selector-max-type
+// stylelint-disable selector-max-type, selector-max-specificity, max-nesting-depth, selector-no-qualifying-type
.details-overlay[open] > summary::before {
position: fixed;
@@ -20,9 +20,86 @@
.details-reset {
// Remove marker added by the display: list-item browser default
- > summary { list-style: none; }
+ > summary {
+ list-style: none;
+ transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
+ transition-property: color, background-color, box-shadow, border-color;
+ // fallback :focus state
+ &:focus {
+ @include focusOutline;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusOutline;
+ }
+
+ &.btn-primary {
+ // fallback :focus state
+ &:focus {
+ @include focusOutlineOnEmphasis;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ box-shadow: none;
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusOutlineOnEmphasis;
+ }
+ }
+ }
// Remove marker added by details polyfill
- > summary::before { display: none; }
+ > summary::before {
+ display: none;
+ }
// Remove marker added by Chrome
- > summary::-webkit-details-marker { display: none; }
+ > summary::-webkit-details-marker {
+ display: none;
+ }
+}
+
+.details-overlay > summary {
+ transition: 80ms cubic-bezier(0.33, 1, 0.68, 1);
+ transition-property: color, background-color, box-shadow, border-color;
+ // fallback :focus state
+ &:focus {
+ @include focusOutline;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusOutline;
+ }
+
+ &.btn-primary {
+ // fallback :focus state
+ &:focus {
+ @include focusOutlineOnEmphasis;
+
+ // remove fallback :focus if :focus-visible is supported
+ &:not(:focus-visible) {
+ outline: solid 1px transparent;
+ box-shadow: none;
+ }
+ }
+
+ // default focus state
+ &:focus-visible {
+ @include focusOutlineOnEmphasis;
+ }
+ }
}
diff --git a/yarn.lock b/yarn.lock
index d098eab482..cccd6dfaee 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1167,10 +1167,10 @@
resolved "https://registry.yarnpkg.com/@primer/primitives/-/primitives-7.7.0.tgz#4e838afaf997036720a43ebab0211d2de77b1606"
integrity sha512-LSd96U2/A70obilbdYiEKI8D/wXUtZnKmUI/ScLOlGDju4iuwd3pJsmFoBwM1Us6vV23V6mapIG+lh27RzauaA==
-"@primer/stylelint-config@^12.3.3":
- version "12.3.3"
- resolved "https://registry.yarnpkg.com/@primer/stylelint-config/-/stylelint-config-12.3.3.tgz#f4ef0548a7884229358e5243d746d6d3af6a73a5"
- integrity sha512-PHcBGEM/8mToW9aVrcRi9RVZN0WpW8zHN1C8+deP6tozclP06jzHsdprJG02buyR6N1BmDllsEBYZ2KK4Y71Bg==
+"@primer/stylelint-config@^12.4.0":
+ version "12.4.0"
+ resolved "https://registry.yarnpkg.com/@primer/stylelint-config/-/stylelint-config-12.4.0.tgz#3a9e8ed20673ed6ea17c703bb754b95b82a8a0f4"
+ integrity sha512-7fjs9KBnMdXLlWbPO7Q9WLS1y4kFitmISdVoADtvLxx0k5bY9XWc0sOhEiazioYo9ipwk80VwF4LlijeaZT7JA==
dependencies:
anymatch "^3.1.1"
globby "^11.0.1"
@@ -1178,7 +1178,7 @@
postcss-scss "^4.0.2"
postcss-value-parser "^4.0.2"
string.prototype.matchall "^4.0.2"
- stylelint-config-standard "24.0.0"
+ stylelint-config-standard "25.0.0"
stylelint-no-unsupported-browser-features "^5.0.1"
stylelint-order "^5.0.0"
stylelint-scss "^4.0.0"
@@ -5383,17 +5383,17 @@ style-search@^0.1.0:
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=
-stylelint-config-recommended@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-6.0.0.tgz#fd2523a322836005ad9bf473d3e5534719c09f9d"
- integrity sha512-ZorSSdyMcxWpROYUvLEMm0vSZud2uB7tX1hzBZwvVY9SV/uly4AvvJPPhCcymZL3fcQhEQG5AELmrxWqtmzacw==
+stylelint-config-recommended@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-7.0.0.tgz#7497372ae83ab7a6fffc18d7d7b424c6480ae15e"
+ integrity sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==
-stylelint-config-standard@24.0.0:
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-24.0.0.tgz#6823f207ab997ae0b641f9a636d007cc44d77541"
- integrity sha512-+RtU7fbNT+VlNbdXJvnjc3USNPZRiRVp/d2DxOF/vBDDTi0kH5RX2Ny6errdtZJH3boO+bmqIYEllEmok4jiuw==
+stylelint-config-standard@25.0.0:
+ version "25.0.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-25.0.0.tgz#2c916984e6655d40d6e8748b19baa8603b680bff"
+ integrity sha512-21HnP3VSpaT1wFjFvv9VjvOGDtAviv47uTp3uFmzcN+3Lt+RYRv6oAplLaV51Kf792JSxJ6svCJh/G18E9VnCA==
dependencies:
- stylelint-config-recommended "^6.0.0"
+ stylelint-config-recommended "^7.0.0"
stylelint-no-unsupported-browser-features@^5.0.1:
version "5.0.2"