From 6dc387486b8ef80a0a1837dab01cf124fa120956 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Tue, 9 Jun 2026 13:28:41 -0500 Subject: [PATCH 1/2] feat: add CSS Layers to 03-internal-inputs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .changeset/css-layers-internal-inputs.md | 5 + packages/react/src/Label/Label.module.css | 132 +++---- .../react/src/__tests__/css-layers.test.ts | 10 + .../src/internal/components/Caret.module.css | 28 +- .../internal/components/InputLabel.module.css | 58 +-- .../components/InputValidation.module.css | 52 +-- .../TextInputInnerAction.module.css | 70 ++-- .../TextInputInnerVisualSlot.module.css | 40 +- .../components/TextInputWrapper.module.css | 358 +++++++++--------- .../UnderlineTabbedInterface.module.css | 276 +++++++------- .../components/UnstyledTextInput.module.css | 22 +- .../ValidationAnimationContainer.module.css | 28 +- 12 files changed, 557 insertions(+), 522 deletions(-) create mode 100644 .changeset/css-layers-internal-inputs.md diff --git a/.changeset/css-layers-internal-inputs.md b/.changeset/css-layers-internal-inputs.md new file mode 100644 index 00000000000..4c2a0a71145 --- /dev/null +++ b/.changeset/css-layers-internal-inputs.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +Caret, InputLabel, InputValidation, TextInputInnerAction, TextInputInnerVisualSlot, TextInputWrapper, UnderlineTabbedInterface, UnstyledTextInput, ValidationAnimationContainer, Label: Improve custom class override behavior for component styles diff --git a/packages/react/src/Label/Label.module.css b/packages/react/src/Label/Label.module.css index 1635e294778..b165ca42fed 100644 --- a/packages/react/src/Label/Label.module.css +++ b/packages/react/src/Label/Label.module.css @@ -1,79 +1,81 @@ -:where(.Label) { - display: inline-flex; - font-size: var(--text-body-size-small); - font-weight: var(--base-text-weight-medium); - /* stylelint-disable-next-line primer/typography */ - line-height: 1; - color: var(--fgColor-default); - white-space: nowrap; - background-color: transparent; - border-style: solid; - border-width: var(--borderWidth-thin); - border-radius: var(--borderRadius-full); - align-items: center; +@layer primer.components.Label { + :where(.Label) { + display: inline-flex; + font-size: var(--text-body-size-small); + font-weight: var(--base-text-weight-medium); + /* stylelint-disable-next-line primer/typography */ + line-height: 1; + color: var(--fgColor-default); + white-space: nowrap; + background-color: transparent; + border-style: solid; + border-width: var(--borderWidth-thin); + border-radius: var(--borderRadius-full); + align-items: center; - &:where([data-size='small']) { - height: var(--base-size-20); - padding: 0 var(--base-size-6); - } + &:where([data-size='small']) { + height: var(--base-size-20); + padding: 0 var(--base-size-6); + } - &:where([data-size='large']) { - height: var(--base-size-24); - padding: 0 var(--base-size-8); - } + &:where([data-size='large']) { + height: var(--base-size-24); + padding: 0 var(--base-size-8); + } - &:where([data-variant='default']) { - border-color: var(--borderColor-default); - } + &:where([data-variant='default']) { + border-color: var(--borderColor-default); + } - &:where([data-variant='primary']) { - /* stylelint-disable-next-line primer/colors */ - border-color: var(--fgColor-default); - } + &:where([data-variant='primary']) { + /* stylelint-disable-next-line primer/colors */ + border-color: var(--fgColor-default); + } - &:where([data-variant='secondary']) { - color: var(--fgColor-muted); - border-color: var(--borderColor-muted); - } + &:where([data-variant='secondary']) { + color: var(--fgColor-muted); + border-color: var(--borderColor-muted); + } - &:where([data-variant='accent']) { - color: var(--fgColor-accent); - /* stylelint-disable-next-line primer/colors */ - border-color: var(--bgColor-accent-emphasis); - } + &:where([data-variant='accent']) { + color: var(--fgColor-accent); + /* stylelint-disable-next-line primer/colors */ + border-color: var(--bgColor-accent-emphasis); + } - &:where([data-variant='success']) { - color: var(--fgColor-success); - /* stylelint-disable-next-line primer/colors */ - border-color: var(--bgColor-success-emphasis); - } + &:where([data-variant='success']) { + color: var(--fgColor-success); + /* stylelint-disable-next-line primer/colors */ + border-color: var(--bgColor-success-emphasis); + } - &:where([data-variant='attention']) { - color: var(--fgColor-attention); - /* stylelint-disable-next-line primer/colors */ - border-color: var(--bgColor-attention-emphasis); - } + &:where([data-variant='attention']) { + color: var(--fgColor-attention); + /* stylelint-disable-next-line primer/colors */ + border-color: var(--bgColor-attention-emphasis); + } - &:where([data-variant='severe']) { - color: var(--fgColor-severe); - /* stylelint-disable-next-line primer/colors */ - border-color: var(--bgColor-severe-emphasis); - } + &:where([data-variant='severe']) { + color: var(--fgColor-severe); + /* stylelint-disable-next-line primer/colors */ + border-color: var(--bgColor-severe-emphasis); + } - &:where([data-variant='danger']) { - color: var(--fgColor-danger); - border-color: var(--borderColor-danger-emphasis); - } + &:where([data-variant='danger']) { + color: var(--fgColor-danger); + border-color: var(--borderColor-danger-emphasis); + } - &:where([data-variant='done']) { - color: var(--fgColor-done); - /* stylelint-disable-next-line primer/colors */ - border-color: var(--bgColor-done-emphasis); - } + &:where([data-variant='done']) { + color: var(--fgColor-done); + /* stylelint-disable-next-line primer/colors */ + border-color: var(--bgColor-done-emphasis); + } - &:where([data-variant='sponsors']) { - color: var(--fgColor-sponsors); - /* stylelint-disable-next-line primer/colors */ - border-color: var(--bgColor-sponsors-emphasis); + &:where([data-variant='sponsors']) { + color: var(--fgColor-sponsors); + /* stylelint-disable-next-line primer/colors */ + border-color: var(--bgColor-sponsors-emphasis); + } } } diff --git a/packages/react/src/__tests__/css-layers.test.ts b/packages/react/src/__tests__/css-layers.test.ts index 3d548f05f0f..27ef21499b8 100644 --- a/packages/react/src/__tests__/css-layers.test.ts +++ b/packages/react/src/__tests__/css-layers.test.ts @@ -25,6 +25,16 @@ const allowlist = new Set([ path.resolve(import.meta.dirname, '../Hidden/Hidden.module.css'), path.resolve(import.meta.dirname, '../InlineMessage/InlineMessage.module.css'), path.resolve(import.meta.dirname, '../internal/components/ButtonReset.module.css'), + path.resolve(import.meta.dirname, '../internal/components/Caret.module.css'), + path.resolve(import.meta.dirname, '../internal/components/InputLabel.module.css'), + path.resolve(import.meta.dirname, '../internal/components/InputValidation.module.css'), + path.resolve(import.meta.dirname, '../internal/components/TextInputInnerAction.module.css'), + path.resolve(import.meta.dirname, '../internal/components/TextInputInnerVisualSlot.module.css'), + path.resolve(import.meta.dirname, '../internal/components/TextInputWrapper.module.css'), + path.resolve(import.meta.dirname, '../internal/components/UnderlineTabbedInterface.module.css'), + path.resolve(import.meta.dirname, '../internal/components/UnstyledTextInput.module.css'), + path.resolve(import.meta.dirname, '../internal/components/ValidationAnimationContainer.module.css'), + path.resolve(import.meta.dirname, '../Label/Label.module.css'), ]) const files = Array.from(allowlist).map(file => { return [path.relative(path.resolve(import.meta.dirname, '..'), file), file] diff --git a/packages/react/src/internal/components/Caret.module.css b/packages/react/src/internal/components/Caret.module.css index d89a0748312..26ec28353cc 100644 --- a/packages/react/src/internal/components/Caret.module.css +++ b/packages/react/src/internal/components/Caret.module.css @@ -1,16 +1,18 @@ -.Caret { - pointer-events: none; - position: absolute; -} +@layer primer.components.Caret { + .Caret { + pointer-events: none; + position: absolute; + } -.CaretTriangle { - /* stylelint-disable-next-line primer/colors */ - fill: var(--caret-bg, var(--bgColor-default)); -} + .CaretTriangle { + /* stylelint-disable-next-line primer/colors */ + fill: var(--caret-bg, var(--bgColor-default)); + } -.CaretBorder { - fill: none; - /* stylelint-disable-next-line primer/colors */ - stroke: var(--caret-border-color, var(--borderColor-default)); - stroke-width: var(--caret-border-width, var(--borderWidth-thin)); + .CaretBorder { + fill: none; + /* stylelint-disable-next-line primer/colors */ + stroke: var(--caret-border-color, var(--borderColor-default)); + stroke-width: var(--caret-border-width, var(--borderWidth-thin)); + } } diff --git a/packages/react/src/internal/components/InputLabel.module.css b/packages/react/src/internal/components/InputLabel.module.css index 4f2833216e0..891de1552fb 100644 --- a/packages/react/src/internal/components/InputLabel.module.css +++ b/packages/react/src/internal/components/InputLabel.module.css @@ -1,33 +1,35 @@ -.Label { - display: block; - font-size: var(--text-body-size-medium); - font-weight: var(--base-text-weight-semibold); - color: var(--fgColor-default); - cursor: pointer; - align-self: flex-start; +@layer primer.components.InputLabel { + .Label { + display: block; + font-size: var(--text-body-size-medium); + font-weight: var(--base-text-weight-semibold); + color: var(--fgColor-default); + cursor: pointer; + align-self: flex-start; - &:where([data-control-disabled]) { - color: var(--control-fgColor-disabled); - cursor: not-allowed; - } + &:where([data-control-disabled]) { + color: var(--control-fgColor-disabled); + cursor: not-allowed; + } - &:where([data-visually-hidden]) { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - /* stylelint-disable-next-line primer/spacing */ - margin: -1px; - overflow: hidden; - /* stylelint-disable-next-line property-no-deprecated */ - clip: rect(0 0 0 0); - white-space: nowrap; - border: 0; - clip-path: inset(50%); + &:where([data-visually-hidden]) { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + /* stylelint-disable-next-line primer/spacing */ + margin: -1px; + overflow: hidden; + /* stylelint-disable-next-line property-no-deprecated */ + clip: rect(0 0 0 0); + white-space: nowrap; + border: 0; + clip-path: inset(50%); + } } -} -.RequiredText { - display: flex; - column-gap: var(--base-size-4); + .RequiredText { + display: flex; + column-gap: var(--base-size-4); + } } diff --git a/packages/react/src/internal/components/InputValidation.module.css b/packages/react/src/internal/components/InputValidation.module.css index 48e10f1361b..cbda1f68217 100644 --- a/packages/react/src/internal/components/InputValidation.module.css +++ b/packages/react/src/internal/components/InputValidation.module.css @@ -1,32 +1,34 @@ -.InputValidation { - display: flex; - font-size: var(--text-body-size-small); - font-weight: var(--base-text-weight-semibold); - /* stylelint-disable-next-line primer/colors */ - color: var(--inputValidation-fgColor); +@layer primer.components.InputValidation { + .InputValidation { + display: flex; + font-size: var(--text-body-size-small); + font-weight: var(--base-text-weight-semibold); + /* stylelint-disable-next-line primer/colors */ + color: var(--inputValidation-fgColor); - & :where(a) { - color: currentColor; - text-decoration: underline; - } + & :where(a) { + color: currentColor; + text-decoration: underline; + } - &:where([data-validation-status='success']) { - --inputValidation-fgColor: var(--fgColor-success); - } + &:where([data-validation-status='success']) { + --inputValidation-fgColor: var(--fgColor-success); + } - &:where([data-validation-status='error']) { - --inputValidation-fgColor: var(--fgColor-danger); + &:where([data-validation-status='error']) { + --inputValidation-fgColor: var(--fgColor-danger); + } } -} -.ValidationIcon { - display: flex; - margin-top: var(--base-size-2); - margin-inline-end: var(--base-size-4); - min-height: var(--inputValidation-iconSize); -} + .ValidationIcon { + display: flex; + margin-top: var(--base-size-2); + margin-inline-end: var(--base-size-4); + min-height: var(--inputValidation-iconSize); + } -.ValidationText { - /* stylelint-disable-next-line primer/typography */ - line-height: var(--inputValidation-lineHeight); + .ValidationText { + /* stylelint-disable-next-line primer/typography */ + line-height: var(--inputValidation-lineHeight); + } } diff --git a/packages/react/src/internal/components/TextInputInnerAction.module.css b/packages/react/src/internal/components/TextInputInnerAction.module.css index 355c7c63214..d20d059c44e 100644 --- a/packages/react/src/internal/components/TextInputInnerAction.module.css +++ b/packages/react/src/internal/components/TextInputInnerAction.module.css @@ -1,42 +1,44 @@ -.Invisible { - position: relative; - padding-top: var(--base-size-2); - padding-right: var(--base-size-4); - padding-bottom: var(--base-size-2); - padding-left: var(--base-size-4); - color: var(--fgColor-muted); - background-color: transparent; +@layer primer.components.TextInputInnerAction { + .Invisible { + position: relative; + padding-top: var(--base-size-2); + padding-right: var(--base-size-4); + padding-bottom: var(--base-size-2); + padding-left: var(--base-size-4); + color: var(--fgColor-muted); + background-color: transparent; - &:hover, - &:focus { - color: var(--fgColor-default); - } + &:hover, + &:focus { + color: var(--fgColor-default); + } - &[data-component='IconButton'] { - width: var(--inner-action-size); - height: var(--inner-action-size); - } + &[data-component='IconButton'] { + width: var(--inner-action-size); + height: var(--inner-action-size); + } - @media (pointer: coarse) { - ::after { - position: absolute; - top: 50%; - right: 0; - left: 0; - min-height: 44px; - content: ''; - transform: translateY(-50%); + @media (pointer: coarse) { + ::after { + position: absolute; + top: 50%; + right: 0; + left: 0; + min-height: 44px; + content: ''; + transform: translateY(-50%); + } } } -} -.TextInputAction { - margin-right: var(--base-size-4); - margin-left: var(--base-size-4); - /* stylelint-disable-next-line primer/typography */ - line-height: 0; -} + .TextInputAction { + margin-right: var(--base-size-4); + margin-left: var(--base-size-4); + /* stylelint-disable-next-line primer/typography */ + line-height: 0; + } -.ConditionalTooltip { - display: inline-block; + .ConditionalTooltip { + display: inline-block; + } } diff --git a/packages/react/src/internal/components/TextInputInnerVisualSlot.module.css b/packages/react/src/internal/components/TextInputInnerVisualSlot.module.css index 02e39d0dd90..cfaa56b9fd9 100644 --- a/packages/react/src/internal/components/TextInputInnerVisualSlot.module.css +++ b/packages/react/src/internal/components/TextInputInnerVisualSlot.module.css @@ -1,24 +1,26 @@ -.Spinner { - position: absolute; - top: 0; - right: 0; - max-width: 100%; - height: 100%; -} +@layer primer.components.TextInputInnerVisualSlot { + .Spinner { + position: absolute; + top: 0; + right: 0; + max-width: 100%; + height: 100%; + } -.SpinnerLeading { - left: 0; -} + .SpinnerLeading { + left: 0; + } -.SpinnerHidden { - visibility: hidden; -} + .SpinnerHidden { + visibility: hidden; + } -.SpinnerVisible { - visibility: visible; -} + .SpinnerVisible { + visibility: visible; + } -.Box { - position: relative; - display: flex; + .Box { + position: relative; + display: flex; + } } diff --git a/packages/react/src/internal/components/TextInputWrapper.module.css b/packages/react/src/internal/components/TextInputWrapper.module.css index 52ee1ff3378..633db5e1068 100644 --- a/packages/react/src/internal/components/TextInputWrapper.module.css +++ b/packages/react/src/internal/components/TextInputWrapper.module.css @@ -1,221 +1,223 @@ -.TextInputBaseWrapper { - display: inline-flex; - min-height: var(--base-size-32); - overflow: hidden; - font-size: var(--text-body-size-medium); - /* stylelint-disable-next-line primer/typography */ - line-height: var(--base-size-20); - color: var(--fgColor-default); - vertical-align: middle; - cursor: text; - background-color: var(--bgColor-default); - border: var(--borderWidth-thin) solid var(--control-borderColor-rest); - border-radius: var(--borderRadius-medium); - outline: none; - box-shadow: var(--shadow-inset); - align-items: stretch; - - input, - textarea { +@layer primer.components.TextInputWrapper { + .TextInputBaseWrapper { + display: inline-flex; + min-height: var(--base-size-32); + overflow: hidden; + font-size: var(--text-body-size-medium); + /* stylelint-disable-next-line primer/typography */ + line-height: var(--base-size-20); + color: var(--fgColor-default); + vertical-align: middle; cursor: text; - } - - select { - cursor: pointer; - } + background-color: var(--bgColor-default); + border: var(--borderWidth-thin) solid var(--control-borderColor-rest); + border-radius: var(--borderRadius-medium); + outline: none; + box-shadow: var(--shadow-inset); + align-items: stretch; - input, - textarea, - select { - &::placeholder { - color: var(--fgColor-muted); + input, + textarea { + cursor: text; } - } - - &:where([data-trailing-action][data-focused]), - &:where([data-no-trailing-action]:focus-within) { - border-color: var(--borderColor-accent-emphasis); - outline: var(--borderWidth-thick) solid var(--borderColor-accent-emphasis); - outline-offset: -1px; - } - > textarea { - padding: var(--base-size-12); - } - - &:where([data-contrast]) { - /* this variable is available behind a feature flag in gh/gh */ - /* stylelint-disable-next-line primer/colors */ - background-color: var(--control-bgColor-contrast, var(--bgColor-inset)); - } - - &:where([data-disabled]) { - color: var(--fgColor-disabled); - background-color: var(--control-bgColor-disabled); - border-color: var(--control-borderColor-disabled); - box-shadow: none; + select { + cursor: pointer; + } input, textarea, select { - cursor: not-allowed; + &::placeholder { + color: var(--fgColor-muted); + } } - } - - &:where([data-monospace]) { - font-family: var(--fontStack-monospace); - } - - &:where([data-validation='error']) { - border-color: var(--borderColor-danger-emphasis); &:where([data-trailing-action][data-focused]), - &:where([data-no-trailing-action]):focus-within { - border-color: var(--control-borderColor-danger); - outline: 2px solid var(--control-borderColor-danger); + &:where([data-no-trailing-action]:focus-within) { + border-color: var(--borderColor-accent-emphasis); + outline: var(--borderWidth-thick) solid var(--borderColor-accent-emphasis); outline-offset: -1px; } - } - - &:where([data-validation='success']) { - /* stylelint-disable-next-line primer/colors */ - border-color: var(--bgColor-success-emphasis); - } - &:where([data-block]) { - display: flex; - width: 100%; - align-self: stretch; - } + > textarea { + padding: var(--base-size-12); + } - /* Ensures inputs don't zoom on mobile but are body-font size on desktop */ - @media screen and (--viewportRange-regular) { - font-size: var(--text-body-size-medium); - } + &:where([data-contrast]) { + /* this variable is available behind a feature flag in gh/gh */ + /* stylelint-disable-next-line primer/colors */ + background-color: var(--control-bgColor-contrast, var(--bgColor-inset)); + } - --inner-action-size: var(--base-size-24); /* Default size */ + &:where([data-disabled]) { + color: var(--fgColor-disabled); + background-color: var(--control-bgColor-disabled); + border-color: var(--control-borderColor-disabled); + box-shadow: none; + + input, + textarea, + select { + cursor: not-allowed; + } + } - &:where([data-size='small']) { - --inner-action-size: var(--base-size-20); + &:where([data-monospace]) { + font-family: var(--fontStack-monospace); + } - min-height: var(--base-size-28); - /* stylelint-disable-next-line primer/spacing */ - padding-top: 3px; - padding-right: var(--base-size-8); - /* stylelint-disable-next-line primer/spacing */ - padding-bottom: 3px; - padding-left: var(--base-size-8); - font-size: var(--text-body-size-small); - /* stylelint-disable-next-line primer/typography */ - line-height: var(--base-size-20); - } + &:where([data-validation='error']) { + border-color: var(--borderColor-danger-emphasis); - &:where([data-size='large']) { - --inner-action-size: var(--base-size-28); + &:where([data-trailing-action][data-focused]), + &:where([data-no-trailing-action]):focus-within { + border-color: var(--control-borderColor-danger); + outline: 2px solid var(--control-borderColor-danger); + outline-offset: -1px; + } + } - height: var(--base-size-40); - /* stylelint-disable-next-line primer/spacing */ - padding-top: 10px; - padding-right: var(--base-size-8); - /* stylelint-disable-next-line primer/spacing */ - padding-bottom: 10px; - padding-left: var(--base-size-8); - } + &:where([data-validation='success']) { + /* stylelint-disable-next-line primer/colors */ + border-color: var(--bgColor-success-emphasis); + } - /* Deprecated */ - &:where([data-variant='small']) { - min-height: 28px; - /* stylelint-disable-next-line primer/spacing */ - padding-top: 3px; - padding-right: var(--base-size-8); - /* stylelint-disable-next-line primer/spacing */ - padding-bottom: 3px; - padding-left: var(--base-size-8); - /* stylelint-disable-next-line declaration-property-value-no-unknown */ - font-size: (--text-body-size-small); - /* stylelint-disable-next-line primer/typography */ - line-height: var(--base-size-20); - } + &:where([data-block]) { + display: flex; + width: 100%; + align-self: stretch; + } - /* Deprecated */ - &:where([data-variant='large']) { - /* stylelint-disable-next-line primer/spacing */ - padding-top: 10px; - padding-right: var(--base-size-8); - /* stylelint-disable-next-line primer/spacing */ - padding-bottom: 10px; - padding-left: var(--base-size-8); - font-size: var(--text-title-size-medium); - } -} + /* Ensures inputs don't zoom on mobile but are body-font size on desktop */ + @media screen and (--viewportRange-regular) { + font-size: var(--text-body-size-medium); + } -.TextInputWrapper { - padding-right: 0; - padding-left: 0; + --inner-action-size: var(--base-size-24); /* Default size */ - > input, - > select { - padding-right: 0; - padding-left: 0; - } + &:where([data-size='small']) { + --inner-action-size: var(--base-size-20); - /* Repeat and position set for form states (success, error, etc) */ - background-repeat: no-repeat; + min-height: var(--base-size-28); + /* stylelint-disable-next-line primer/spacing */ + padding-top: 3px; + padding-right: var(--base-size-8); + /* stylelint-disable-next-line primer/spacing */ + padding-bottom: 3px; + padding-left: var(--base-size-8); + font-size: var(--text-body-size-small); + /* stylelint-disable-next-line primer/typography */ + line-height: var(--base-size-20); + } - /* For form validation. This keeps images 8px from right and centered vertically. */ - background-position: right 8px center; + &:where([data-size='large']) { + --inner-action-size: var(--base-size-28); - & > :not(:last-child) { - margin-right: var(--base-size-8); - } + height: var(--base-size-40); + /* stylelint-disable-next-line primer/spacing */ + padding-top: 10px; + padding-right: var(--base-size-8); + /* stylelint-disable-next-line primer/spacing */ + padding-bottom: 10px; + padding-left: var(--base-size-8); + } - & :global(.TextInput-icon) /* stylelint-disable-line selector-class-pattern */, - & :global(.TextInput-action) /* stylelint-disable-line selector-class-pattern */ { - align-self: center; - color: var(--fgColor-muted); - flex-shrink: 0; - } + /* Deprecated */ + &:where([data-variant='small']) { + min-height: 28px; + /* stylelint-disable-next-line primer/spacing */ + padding-top: 3px; + padding-right: var(--base-size-8); + /* stylelint-disable-next-line primer/spacing */ + padding-bottom: 3px; + padding-left: var(--base-size-8); + /* stylelint-disable-next-line declaration-property-value-no-unknown */ + font-size: (--text-body-size-small); + /* stylelint-disable-next-line primer/typography */ + line-height: var(--base-size-20); + } - /* With leading visual */ - &:where([data-leading-visual]) { - padding-left: var(--base-size-8); + /* Deprecated */ + &:where([data-variant='large']) { + /* stylelint-disable-next-line primer/spacing */ + padding-top: 10px; + padding-right: var(--base-size-8); + /* stylelint-disable-next-line primer/spacing */ + padding-bottom: 10px; + padding-left: var(--base-size-8); + font-size: var(--text-title-size-medium); + } } - /* With trailing visual */ - &:where([data-trailing-visual][data-no-trailing-action]) { - padding-right: var(--base-size-8); - } + .TextInputWrapper { + padding-right: 0; + padding-left: 0; - /* Only trailing visual */ - &:where([data-no-leading-visual][data-trailing-visual]), - &:where([data-no-leading-visual][data-trailing-action]) { > input, > select { + padding-right: 0; + padding-left: 0; + } + + /* Repeat and position set for form states (success, error, etc) */ + background-repeat: no-repeat; + + /* For form validation. This keeps images 8px from right and centered vertically. */ + background-position: right 8px center; + + & > :not(:last-child) { + margin-right: var(--base-size-8); + } + + & :global(.TextInput-icon) /* stylelint-disable-line selector-class-pattern */, + & :global(.TextInput-action) /* stylelint-disable-line selector-class-pattern */ { + align-self: center; + color: var(--fgColor-muted); + flex-shrink: 0; + } + + /* With leading visual */ + &:where([data-leading-visual]) { padding-left: var(--base-size-8); } - } - /* Only leading visual */ - &:where([data-no-trailing-visual][data-no-trailing-action]) > input, - &:where([data-no-trailing-visual][data-no-trailing-action]) > select { - padding-right: var(--base-size-8); - } + /* With trailing visual */ + &:where([data-trailing-visual][data-no-trailing-action]) { + padding-right: var(--base-size-8); + } - /* No visuals at all */ - &:where([data-no-leading-visual][data-no-trailing-visual][data-no-trailing-action]) > input, - &:where([data-no-leading-visual][data-no-trailing-visual][data-no-trailing-action]) > select { - padding-left: var(--base-size-12); - padding-right: var(--base-size-12); - } -} + /* Only trailing visual */ + &:where([data-no-leading-visual][data-trailing-visual]), + &:where([data-no-leading-visual][data-trailing-action]) { + > input, + > select { + padding-left: var(--base-size-8); + } + } + + /* Only leading visual */ + &:where([data-no-trailing-visual][data-no-trailing-action]) > input, + &:where([data-no-trailing-visual][data-no-trailing-action]) > select { + padding-right: var(--base-size-8); + } -/* Large size input */ -.TextInputWrapper:where([data-size='large']) { - &:where([data-leading-visual]) { - padding-left: var(--base-size-12); + /* No visuals at all */ + &:where([data-no-leading-visual][data-no-trailing-visual][data-no-trailing-action]) > input, + &:where([data-no-leading-visual][data-no-trailing-visual][data-no-trailing-action]) > select { + padding-left: var(--base-size-12); + padding-right: var(--base-size-12); + } } - &:where([data-trailing-visual][data-no-trailing-action]) { - padding-right: var(--base-size-12); + /* Large size input */ + .TextInputWrapper:where([data-size='large']) { + &:where([data-leading-visual]) { + padding-left: var(--base-size-12); + } + + &:where([data-trailing-visual][data-no-trailing-action]) { + padding-right: var(--base-size-12); + } } } diff --git a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css index 83cdd50d81f..40c95508e9a 100644 --- a/packages/react/src/internal/components/UnderlineTabbedInterface.module.css +++ b/packages/react/src/internal/components/UnderlineTabbedInterface.module.css @@ -1,167 +1,169 @@ -.UnderlineWrapper { - display: flex; - /* stylelint-disable-next-line primer/spacing */ - padding-inline: var(--stack-padding-normal); - justify-content: flex-start; - align-items: center; +@layer primer.components.UnderlineTabbedInterface { + .UnderlineWrapper { + display: flex; + /* stylelint-disable-next-line primer/spacing */ + padding-inline: var(--stack-padding-normal); + justify-content: flex-start; + align-items: center; - /* make space for the underline */ - min-height: var(--control-xlarge-size, 48px); + /* make space for the underline */ + min-height: var(--control-xlarge-size, 48px); - /* using a box-shadow instead of a border to accommodate 'overflow-y: hidden' on UnderlinePanels */ - /* stylelint-disable-next-line primer/box-shadow */ - box-shadow: inset 0 -1px var(--borderColor-muted); + /* using a box-shadow instead of a border to accommodate 'overflow-y: hidden' on UnderlinePanels */ + /* stylelint-disable-next-line primer/box-shadow */ + box-shadow: inset 0 -1px var(--borderColor-muted); - /* Hide overflow until calculation is complete to prevent CLS */ - overflow: visible; + /* Hide overflow until calculation is complete to prevent CLS */ + overflow: visible; - &[data-overflow-measured='false'] { - overflow: hidden; + &[data-overflow-measured='false'] { + overflow: hidden; + } + + &[data-overflow-measured='true'] { + overflow: visible; + } + + &[data-variant='flush'] { + /* stylelint-disable-next-line primer/spacing */ + padding-inline: unset; + } } - &[data-overflow-measured='true'] { - overflow: visible; + .UnderlineItemList { + position: relative; + display: flex; + padding: 0; + margin: 0; + white-space: nowrap; + list-style: none; + align-items: center; + gap: 8px; } - &[data-variant='flush'] { - /* stylelint-disable-next-line primer/spacing */ - padding-inline: unset; + .UnderlineItem { + /* underline tab specific styles */ + position: relative; + display: inline-flex; + font: inherit; + font-size: var(--text-body-size-medium); + line-height: var(--text-body-lineHeight-medium, 1.4285); + color: var(--fgColor-default); + text-align: center; + text-decoration: none; + cursor: pointer; + background-color: transparent; + border: 0; + border-radius: var(--borderRadius-medium, var(--borderRadius-small)); + + /* button resets */ + appearance: none; + padding-inline: var(--base-size-8); + padding-block: var(--base-size-6); + align-items: center; + + @media (hover: hover) { + &:hover { + text-decoration: none; + background-color: var(--bgColor-neutral-muted); + transition: background-color 0.12s ease-out; + } + } } -} -.UnderlineItemList { - position: relative; - display: flex; - padding: 0; - margin: 0; - white-space: nowrap; - list-style: none; - align-items: center; - gap: 8px; -} + .UnderlineItem:focus { + outline: 2px solid transparent; + /* stylelint-disable-next-line primer/box-shadow */ + box-shadow: inset 0 0 0 2px var(--fgColor-accent); -.UnderlineItem { - /* underline tab specific styles */ - position: relative; - display: inline-flex; - font: inherit; - font-size: var(--text-body-size-medium); - line-height: var(--text-body-lineHeight-medium, 1.4285); - color: var(--fgColor-default); - text-align: center; - text-decoration: none; - cursor: pointer; - background-color: transparent; - border: 0; - border-radius: var(--borderRadius-medium, var(--borderRadius-small)); - - /* button resets */ - appearance: none; - padding-inline: var(--base-size-8); - padding-block: var(--base-size-6); - align-items: center; - - @media (hover: hover) { - &:hover { - text-decoration: none; - background-color: var(--bgColor-neutral-muted); - transition: background-color 0.12s ease-out; + /* where focus-visible is supported, remove the focus box-shadow */ + &:not(:focus-visible) { + box-shadow: none; } } -} -.UnderlineItem:focus { - outline: 2px solid transparent; - /* stylelint-disable-next-line primer/box-shadow */ - box-shadow: inset 0 0 0 2px var(--fgColor-accent); + .UnderlineItem:focus-visible { + outline: 2px solid transparent; + /* stylelint-disable-next-line primer/box-shadow */ + box-shadow: inset 0 0 0 2px var(--fgColor-accent); + } - /* where focus-visible is supported, remove the focus box-shadow */ - &:not(:focus-visible) { - box-shadow: none; + /* renders a visibly hidden "copy" of the label in bold, reserving box space for when label becomes bold on selected */ + .UnderlineItem [data-content]::before { + display: block; + height: 0; + font-weight: var(--base-text-weight-semibold); + white-space: nowrap; + visibility: hidden; + content: attr(data-content); } -} -.UnderlineItem:focus-visible { - outline: 2px solid transparent; - /* stylelint-disable-next-line primer/box-shadow */ - box-shadow: inset 0 0 0 2px var(--fgColor-accent); -} + .UnderlineItem [data-component='icon'] { + display: inline-flex; + color: var(--fgColor-muted); + align-items: center; + margin-inline-end: var(--base-size-8); + } -/* renders a visibly hidden "copy" of the label in bold, reserving box space for when label becomes bold on selected */ -.UnderlineItem [data-content]::before { - display: block; - height: 0; - font-weight: var(--base-text-weight-semibold); - white-space: nowrap; - visibility: hidden; - content: attr(data-content); -} + .UnderlineItem [data-component='counter'] { + margin-inline-start: var(--base-size-8); + display: flex; + align-items: center; + } -.UnderlineItem [data-component='icon'] { - display: inline-flex; - color: var(--fgColor-muted); - align-items: center; - margin-inline-end: var(--base-size-8); -} + .UnderlineItem::after { + position: absolute; + right: 50%; -.UnderlineItem [data-component='counter'] { - margin-inline-start: var(--base-size-8); - display: flex; - align-items: center; -} + /* TODO: see if we can simplify this positioning */ -.UnderlineItem::after { - position: absolute; - right: 50%; - - /* TODO: see if we can simplify this positioning */ - - /* 48px total height / 2 (24px) + 1px */ - /* stylelint-disable-next-line primer/spacing */ - bottom: calc(50% - calc(var(--control-xlarge-size, var(--base-size-48)) / 2 + 1px)); - width: 100%; - height: 2px; - pointer-events: none; - content: ''; - background-color: transparent; - border-radius: 0; - transform: translate(50%, -50%); -} + /* 48px total height / 2 (24px) + 1px */ + /* stylelint-disable-next-line primer/spacing */ + bottom: calc(50% - calc(var(--control-xlarge-size, var(--base-size-48)) / 2 + 1px)); + width: 100%; + height: 2px; + pointer-events: none; + content: ''; + background-color: transparent; + border-radius: 0; + transform: translate(50%, -50%); + } -.UnderlineItem[aria-current]:not([aria-current='false']) [data-component='text'], -.UnderlineItem[aria-selected='true'] [data-component='text'] { - font-weight: var(--base-text-weight-semibold); -} + .UnderlineItem[aria-current]:not([aria-current='false']) [data-component='text'], + .UnderlineItem[aria-selected='true'] [data-component='text'] { + font-weight: var(--base-text-weight-semibold); + } -.UnderlineItem[aria-current]:not([aria-current='false'])::after, -.UnderlineItem[aria-selected='true']::after { - /* stylelint-disable-next-line primer/colors */ - background-color: var(--underlineNav-borderColor-active, var(--color-primer-border-active, #fd8c73)); + .UnderlineItem[aria-current]:not([aria-current='false'])::after, + .UnderlineItem[aria-selected='true']::after { + /* stylelint-disable-next-line primer/colors */ + background-color: var(--underlineNav-borderColor-active, var(--color-primer-border-active, #fd8c73)); - @media (forced-colors: active) { - /* Support for Window Force Color Mode https://learn.microsoft.com/en-us/fluent-ui/web-components/design-system/high-contrast */ - background-color: LinkText; + @media (forced-colors: active) { + /* Support for Window Force Color Mode https://learn.microsoft.com/en-us/fluent-ui/web-components/design-system/high-contrast */ + background-color: LinkText; + } } -} - -.LoadingCounter { - display: inline-block; - width: 1.5rem; - height: 1rem; - /* 16px */ - background-color: var(--bgColor-neutral-muted); - border-color: var(--borderColor-default); - /* stylelint-disable-next-line primer/borders */ - border-radius: 20px; - animation: loadingCounterKeyFrames 1.2s ease-in-out infinite alternate; -} -@keyframes loadingCounterKeyFrames { - from { - opacity: 1; + .LoadingCounter { + display: inline-block; + width: 1.5rem; + height: 1rem; + /* 16px */ + background-color: var(--bgColor-neutral-muted); + border-color: var(--borderColor-default); + /* stylelint-disable-next-line primer/borders */ + border-radius: 20px; + animation: loadingCounterKeyFrames 1.2s ease-in-out infinite alternate; } - to { - opacity: 0.2; + @keyframes loadingCounterKeyFrames { + from { + opacity: 1; + } + + to { + opacity: 0.2; + } } } diff --git a/packages/react/src/internal/components/UnstyledTextInput.module.css b/packages/react/src/internal/components/UnstyledTextInput.module.css index c0b100c57fa..429ad414c46 100644 --- a/packages/react/src/internal/components/UnstyledTextInput.module.css +++ b/packages/react/src/internal/components/UnstyledTextInput.module.css @@ -1,13 +1,15 @@ -.Input { - width: 100%; - font-family: inherit; - font-size: inherit; - color: inherit; - background-color: transparent; - border: 0; - appearance: none; +@layer primer.components.UnstyledTextInput { + .Input { + width: 100%; + font-family: inherit; + font-size: inherit; + color: inherit; + background-color: transparent; + border: 0; + appearance: none; - &:focus { - outline: 0; + &:focus { + outline: 0; + } } } diff --git a/packages/react/src/internal/components/ValidationAnimationContainer.module.css b/packages/react/src/internal/components/ValidationAnimationContainer.module.css index 32cbe1cbce6..6b3b65201d0 100644 --- a/packages/react/src/internal/components/ValidationAnimationContainer.module.css +++ b/packages/react/src/internal/components/ValidationAnimationContainer.module.css @@ -1,19 +1,21 @@ -.Animation:where([data-show]) { - animation: 170ms fadeIn cubic-bezier(0.44, 0.74, 0.36, 1); +@layer primer.components.ValidationAnimationContainer { + .Animation:where([data-show]) { + animation: 170ms fadeIn cubic-bezier(0.44, 0.74, 0.36, 1); - @media (prefers-reduced-motion) { - animation: none; + @media (prefers-reduced-motion) { + animation: none; + } } -} -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(-100%); - } + @keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-100%); + } - to { - opacity: 1; - transform: translateY(0); + to { + opacity: 1; + transform: translateY(0); + } } } From a7390c8f7a2cd6f8e43ddaa2da9499fdb34961e2 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Tue, 9 Jun 2026 15:12:49 -0500 Subject: [PATCH 2/2] chore: standardize CSS layer changeset wording Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .changeset/css-layers-internal-inputs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/css-layers-internal-inputs.md b/.changeset/css-layers-internal-inputs.md index 4c2a0a71145..65e3a8bbaba 100644 --- a/.changeset/css-layers-internal-inputs.md +++ b/.changeset/css-layers-internal-inputs.md @@ -2,4 +2,4 @@ '@primer/react': patch --- -Caret, InputLabel, InputValidation, TextInputInnerAction, TextInputInnerVisualSlot, TextInputWrapper, UnderlineTabbedInterface, UnstyledTextInput, ValidationAnimationContainer, Label: Improve custom class override behavior for component styles +Caret, InputLabel, InputValidation, TextInputInnerAction, TextInputInnerVisualSlot, TextInputWrapper, UnderlineTabbedInterface, UnstyledTextInput, ValidationAnimationContainer, Label: Add CSS layer support for component styles