Skip to content

CSS linting for React components library - #834

Merged
ty2k merged 24 commits into
mainfrom
feature/react-components-css-linting
Aug 17, 2026
Merged

CSS linting for React components library#834
ty2k merged 24 commits into
mainfrom
feature/react-components-css-linting

Conversation

@ty2k

@ty2k ty2k commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

As part of investigating #833, I found we were not codifying our CSS conventions in the React components library. This pull request adds linting via stylelint with a rule set that extends their stylelint-config-standard and adds our own rules to it. The current npm run lint:css script should return no errors. The lint script is updated to run both our TypeScript linting and CSS linting, so the existing GitHub Actions workflow that runs npm run lint will continue to work as expected.

Changes to existing styles to pass new rules

None of these changes will have any visual effect:

  • AccordionGroup.css
    • declaration-block-no-redundant-longhand-properties
      • Combine margin-block-start and margin-block-end
  • AlertBanner.css
    • declaration-block-no-redundant-longhand-properties
      • Combine align-self and justify-self
  • AlertDialog.css
    • declaration-block-no-redundant-longhand-properties
      • Combine align-self and justify-self
  • Calendar.css
    • declaration-block-no-redundant-longhand-properties
      • Combine align-self and justify-self
  • Footer.css
    • declaration-block-no-redundant-longhand-properties
      • Combine flex-direction and flex-wrap to flex-flow
      • Combine row-gap and column-gap to gap
      • Combine grid-column-gap and grid-row-gap to grid-gap
    • property-no-deprecated
      • Use gap instead of deprecated grid-gap
    • media-feature-range-notation
      • Use width <= instead of max-width
  • Header.css
    • declaration-block-no-redundant-longhand-properties
      • Combine border-bottom-color, border-bottom-style, and border-bottom-width to border-bottom
  • SvgBcLogo.css
    • color-hex-length
      • Move to shorthand #fff to match default rule
    • length-zero-no-unit
      • Drop units on 0 to match default rule
  • Radio.css
    • selector-pseudo-element-colon-notation
      • Use modern double colon notation for ::before pseudo-element
  • RadioGroup.css
    • no-duplicate-selectors
      • Remove erroneous duplicate selector
  • Tag.css
    • custom-property-pattern
      • Fix erroneous token variable reference
  • ToggleButton.css
    • declaration-block-no-redundant-longhand-properties
      • Combine flex-direction and flex-wrap to flex-flow

Future work

I added one TODO in the rule set here: https://github.com/bcgov/design-system/blob/feature/react-components-css-linting/packages/react-components/stylelint.config.mjs#L18

I think that no-descending-specificity is a good rule to follow, but there's too much refactoring to include it in this PR. We can endeavour to tighten up that rule after fixing the issues it flags. See below for example output with that rule turned on.

➜ npm run lint:css                                          

> @bcgov/design-system-react-components@0.8.1 lint:css
> stylelint "src/**/*.css"


src/components/AlertDialog/AlertDialog.css
  39:1  ✖  Expected selector ".bcds-react-aria-AlertDialog--Icon" to come before selector                            no-descending-specificity
           ".bcds-react-aria-AlertDialog.info .bcds-react-aria-AlertDialog--Icon", at line 7

src/components/AlertBanner/AlertBanner.css
  68:1  ✖  Expected selector ".bcds-Alert-Banner--closeIcon svg" to come before selector                             no-descending-specificity
           ".bcds-Alert-Banner--closeIcon > .bcds-react-aria-Button[data-hovered] svg", at line 64

src/components/Checkbox/Checkbox.css
   51:1  ✖  Expected selector ".bcds-react-aria-Checkbox[data-pressed] > .checkbox" to come before selector          no-descending-specificity
            ".bcds-react-aria-Checkbox[data-selected][data-hovered] > .checkbox", at line 45
   56:1  ✖  Expected selector ".bcds-react-aria-Checkbox[data-selected] > .checkbox" to come before selector         no-descending-specificity
            ".bcds-react-aria-Checkbox[data-selected][data-hovered] > .checkbox", at line 45
   68:1  ✖  Expected selector ".bcds-react-aria-Checkbox[data-disabled] > .checkbox" to come before selector         no-descending-specificity
            ".bcds-react-aria-Checkbox[data-selected][data-hovered] > .checkbox", at line 45
   84:1  ✖  Expected selector ".bcds-react-aria-Checkbox[data-indeterminate] > .checkbox" to come before selector    no-descending-specificity
            ".bcds-react-aria-Checkbox[data-selected][data-hovered] > .checkbox", at line 45
   95:1  ✖  Expected selector ".bcds-react-aria-Checkbox[data-indeterminate] > .checkbox > svg" to come before       no-descending-specificity
            selector ".bcds-react-aria-Checkbox[data-selected][data-disabled] > .checkbox > svg", at line 79
  110:1  ✖  Expected selector ".bcds-react-aria-Checkbox[data-invalid] > .checkbox" to come before selector          no-descending-specificity
            ".bcds-react-aria-Checkbox[data-selected][data-hovered] > .checkbox", at line 45
  113:1  ✖  Expected selector ".bcds-react-aria-Checkbox[data-invalid] > .checkbox > svg" to come before selector    no-descending-specificity
            ".bcds-react-aria-Checkbox[data-selected][data-disabled] > .checkbox > svg", at line 79

src/components/DatePicker/DatePicker.css
  119:1  ✖  Expected selector ".bcds-react-aria-DatePicker[data-readonly]   > .bcds-react-aria-DatePicker--Group"    no-descending-specificity
            to come before selector ".bcds-react-aria-DatePicker[data-focus-visible]   >
            .bcds-react-aria-DatePicker--Group:has(     .bcds-react-aria-Button[data-focus-visible]   )", at line
            96
  127:1  ✖  Expected selector ".bcds-react-aria-DatePicker[data-disabled]   > .bcds-react-aria-DatePicker--Group"    no-descending-specificity
            to come before selector ".bcds-react-aria-DatePicker[data-focus-visible]   >
            .bcds-react-aria-DatePicker--Group:has(     .bcds-react-aria-Button[data-focus-visible]   )", at line
            96

src/components/Footer/Footer.css
  211:1  ✖  Expected selector ".bcds-footer   > .bcds-footer--container   > .bcds-footer--container-content   >      no-descending-specificity
            .bcds-footer--logo-links   > .bcds-footer--links   > ul   > li   > a" to come before selector
            ".bcds-footer   > .bcds-footer--container   > .bcds-footer--container-content   >
            .bcds-footer--logo-links   > .bcds-footer--logo   > p   > a:hover", at line 131

src/components/MenuItem/MenuItem.css
  48:1  ✖  Expected selector "a.bcds-react-aria-MenuItem   > .bcds-react-aria-MenuItem--Content   >                  no-descending-specificity
           *[slot="description"]" to come before selector ".bcds-react-aria-MenuItem.small   >
           .bcds-react-aria-MenuItem--Content   > *[slot="description"]", at line 23

src/components/NumberField/NumberField.css
  190:1  ✖  Expected selector ".bcds-react-aria-NumberField--Group   > .bcds-react-aria-NumberField--Button   >      no-descending-specificity
            .bcds-react-aria-Button[data-disabled]" to come before selector
            ".bcds-react-aria-NumberField--Group[data-hovered]   > .bcds-react-aria-NumberField--Button   >
            .bcds-react-aria-Button[data-disabled]", at line 166
  199:1  ✖  Expected selector ".bcds-react-aria-NumberField--Group[data-invalid]   >                                 no-descending-specificity
            .bcds-react-aria-NumberField--Button   > .bcds-react-aria-Button" to come before selector
            ".bcds-react-aria-NumberField--Group[data-hovered][data-focus-within]   >
            .bcds-react-aria-NumberField--Button   > .bcds-react-aria-Button", at line 177

src/components/Select/Select.css
   82:1  ✖  Expected selector ".bcds-react-aria-Select--Button" to come before selector                              no-descending-specificity
            ".bcds-react-aria-Select.small .bcds-react-aria-Select--Button", at line 58
  119:1  ✖  Expected selector ".bcds-react-aria-Select--Button > .bcds-react-aria-SelectValue" to come before        no-descending-specificity
            selector ".bcds-react-aria-Select.scroll .bcds-react-aria-SelectValue", at line 13
  136:1  ✖  Expected selector ".bcds-react-aria-Select--InputContainer > .bcds-react-aria-Select--Button" to come    no-descending-specificity
            before selector ".bcds-react-aria-Select.small .bcds-react-aria-Select--Button", at line 58
  149:1  ✖  Expected selector ".bcds-react-aria-Select--TagOverlay" to come before selector                          no-descending-specificity
            ".bcds-react-aria-Select.scroll .bcds-react-aria-Select--TagOverlay", at line 25
  154:1  ✖  Expected selector ".bcds-react-aria-Select--TagOverlay .bcds-react-aria-Tag" to come before selector     no-descending-specificity
            ".bcds-react-aria-Select.scroll .bcds-react-aria-Tag", at line 50
  222:1  ✖  Expected selector ".bcds-react-aria-Select--ListBoxItem" to come before selector                         no-descending-specificity
            ".bcds-react-aria-Select--ListBox.small .bcds-react-aria-Select--ListBoxItem", at line 61

src/components/Switch/Switch.css
  77:1  ✖  Expected selector ".bcds-react-aria-Switch[data-focus-visible] > .indicator" to come before selector      no-descending-specificity
           ".bcds-react-aria-Switch[data-hovered][data-selected] > .indicator", at line 62
  89:1  ✖  Expected selector ".bcds-react-aria-Switch[data-disabled] > .indicator" to come before selector           no-descending-specificity
           ".bcds-react-aria-Switch[data-hovered][data-selected] > .indicator", at line 62
  93:1  ✖  Expected selector ".bcds-react-aria-Switch[data-disabled] > .indicator::before" to come before selector   no-descending-specificity
           ".bcds-react-aria-Switch[data-hovered][data-selected] > .indicator::before", at line 71

src/components/TextArea/TextArea.css
   81:1  ✖  Expected selector ".bcds-react-aria-TextArea--Container:has(   >                                         no-descending-specificity
            .bcds-react-aria-TextArea--Input[data-focus-visible] )" to come before selector
            ".bcds-react-aria-TextArea--Container:has(   >
            .bcds-react-aria-TextArea--Input[data-focused][data-hovered] )", at line 72
   92:1  ✖  Expected selector ".bcds-react-aria-TextArea--Container:has(   >                                         no-descending-specificity
            .bcds-react-aria-TextArea--Input[data-hovered] )" to come before selector
            ".bcds-react-aria-TextArea--Container:has(   >
            .bcds-react-aria-TextArea--Input[data-focused][data-hovered] )", at line 72
   99:1  ✖  Expected selector ".bcds-react-aria-TextArea[data-disabled] > .bcds-react-aria-TextArea--Container" to   no-descending-specificity
            come before selector ".bcds-react-aria-TextArea--Container:has(   >
            .bcds-react-aria-TextArea--Input[data-focused][data-hovered] )", at line 72
  106:1  ✖  Expected selector ".bcds-react-aria-TextArea[data-invalid] > .bcds-react-aria-TextArea--Container" to    no-descending-specificity
            come before selector ".bcds-react-aria-TextArea--Container:has(   >
            .bcds-react-aria-TextArea--Input[data-focused][data-hovered] )", at line 72
  113:1  ✖  Expected selector ".bcds-react-aria-TextArea[data-readonly] > .bcds-react-aria-TextArea--Container" to   no-descending-specificity
            come before selector ".bcds-react-aria-TextArea--Container:has(   >
            .bcds-react-aria-TextArea--Input[data-focused][data-hovered] )", at line 72

src/components/TextField/TextField.css
   84:1  ✖  Expected selector ".bcds-react-aria-TextField--container:has(   >                                        no-descending-specificity
            .bcds-react-aria-TextField--Input[data-focus-visible] )" to come before selector
            ".bcds-react-aria-TextField--container:has(   >
            .bcds-react-aria-TextField--Input[data-focused][data-hovered] )", at line 75
   95:1  ✖  Expected selector ".bcds-react-aria-TextField--container:hover" to come before selector                  no-descending-specificity
            ".bcds-react-aria-TextField--container:has(   > .bcds-react-aria-TextField--Input[data-focused] )", at
            line 75
  101:1  ✖  Expected selector ".bcds-react-aria-TextField[data-disabled]   > .bcds-react-aria-TextField--container"  no-descending-specificity
            to come before selector ".bcds-react-aria-TextField--container:has(   >
            .bcds-react-aria-TextField--Input[data-focused][data-hovered] )", at line 75
  109:1  ✖  Expected selector ".bcds-react-aria-TextField[data-invalid]   > .bcds-react-aria-TextField--container"   no-descending-specificity
            to come before selector ".bcds-react-aria-TextField--container:has(   >
            .bcds-react-aria-TextField--Input[data-focused][data-hovered] )", at line 75
  117:1  ✖  Expected selector ".bcds-react-aria-TextField[data-readonly]   > .bcds-react-aria-TextField--container"  no-descending-specificity
            to come before selector ".bcds-react-aria-TextField--container:has(   >
            .bcds-react-aria-TextField--Input[data-focused][data-hovered] )", at line 75

✖ 34 problems (34 errors, 0 warnings)

@ty2k
ty2k requested review from mkernohanbc and a balanced review from Copilot August 14, 2026 22:49
@ty2k ty2k self-assigned this Aug 14, 2026
@ty2k
ty2k requested a review from a team as a code owner August 14, 2026 22:49
@ty2k ty2k added the react-components Changes or issues affecting the design-system-react-components package label Aug 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Stylelint-based CSS validation to the React component library and integrates it into existing CI linting.

Changes:

  • Adds Stylelint configuration and dependencies.
  • Updates lint scripts and documentation.
  • Normalizes existing CSS to satisfy lint rules.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/react-components/stylelint.config.mjs Defines CSS lint rules.
packages/react-components/package.json Adds Stylelint dependencies and scripts.
packages/react-components/package-lock.json Locks new dependencies.
packages/react-components/README.md Documents lint commands.
packages/react-components/src/components/AccordionGroup/AccordionGroup.css Simplifies logical margins.
packages/react-components/src/components/AlertBanner/AlertBanner.css Consolidates icon alignment.
packages/react-components/src/components/AlertDialog/AlertDialog.css Consolidates icon alignment.
packages/react-components/src/components/Calendar/Calendar.css Uses flex-flow shorthand.
packages/react-components/src/components/Footer/Footer.css Modernizes layout and media syntax.
packages/react-components/src/components/Header/Header.css Uses border shorthand.
packages/react-components/src/components/Icons/SvgBcLogo/SvgBcLogo.css Normalizes zero and color values.
packages/react-components/src/components/Radio/Radio.css Uses modern pseudo-element syntax.
packages/react-components/src/components/RadioGroup/RadioGroup.css Removes a duplicate selector.
packages/react-components/src/components/Tag/Tag.css Replaces a mistyped radius variable.
packages/react-components/src/components/ToggleButton/ToggleButton.css Uses flex-flow shorthand.
Files not reviewed (1)
  • packages/react-components/package-lock.json: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/react-components/src/components/AlertBanner/AlertBanner.css Outdated
Comment thread packages/react-components/src/components/RadioGroup/RadioGroup.css
Comment thread packages/react-components/src/components/Tag/Tag.css Outdated

@mkernohanbc mkernohanbc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all looks eminently sensible to me. I also agree that no-descending-specificity looks like a good guardrail — I've added that refactoring work to the backlog in #841.

@ty2k
ty2k merged commit 7a20b1b into main Aug 17, 2026
5 checks passed
@ty2k
ty2k deleted the feature/react-components-css-linting branch August 17, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

react-components Changes or issues affecting the design-system-react-components package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants