CSS/SCSS patterns that can be modernized for v6
59
no-descending-specificity
38
alpha-value-notation
20
selector-not-notation
9
declaration-block-no-redundant-longhand-properties
7
color-function-notation
3
scss/no-global-function-names
no-descending-specificity
high impact
manual fix
59
A selector with lower specificity appears after one with higher specificity targeting the same element. The later rule may not override the earlier one as intended, leading to unexpected styling. This is a common source of "why isn't my CSS working?" bugs.
MDN Documentation →.card-body:not(:first-child) { ... }
.card-body { padding: 1rem; } /* won't override above */.card-body { padding: 1rem; } /* general first */
.card-body:not(:first-child) { ... } /* specific after */| File | Line | Current code | Suggested fix |
|---|---|---|---|
| scss/_card.scss | 149:5 | .card-body, |
Reorder: move before line 102 |
| scss/_card.scss | 150:5 | .card-list { |
Reorder: move before line 102 |
| scss/_card.scss | 201:5 | .card-body, |
Reorder: move before line 102 |
| scss/_card.scss | 202:5 | .card-list { |
Reorder: move before line 102 |
| scss/_carousel.scss | 160:3 | .carousel-control-prev { |
Reorder: move before line 152 |
| scss/_carousel.scss | 165:3 | .carousel-control-next { |
Reorder: move before line 152 |
| scss/_datepicker.scss | 338:3 | [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 356:5 | &:hover { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 362:3 | [data-vc-date-today] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 370:3 | [data-vc-date-month="next"] [data-vc-date-btn], |
Reorder: move before line 330 |
| scss/_datepicker.scss | 371:3 | [data-vc-date-month="prev"] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 376:3 | [data-vc-date-disabled] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 381:3 | [data-vc-date-hover] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 386:3 | [data-vc-date-hover="first"] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 391:3 | [data-vc-date-hover="last"] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 396:3 | [data-vc-date-hover="first-and-last"] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 400:3 | [data-vc-date-selected="middle"] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 406:3 | [data-vc-date-selected] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 412:3 | [data-vc-date-selected="first"] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 419:3 | [data-vc-date-selected="last"] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_datepicker.scss | 426:3 | [data-vc-date-selected="first-and-last"] [data-vc-date-btn] { |
Reorder: move before line 330 |
| scss/_dialog.scss | 217:3 | .dialog-body { |
Reorder: move before line 151 |
| scss/_nav.scss | 174:5 | .show > .nav-link { |
Reorder: move before line 146 |
| scss/_nav.scss | 194:5 | .nav-link { |
Reorder: move before line 138 |
| scss/_nav.scss | 199:7 | &:hover, |
Reorder: move before line 146 |
| scss/_nav.scss | 200:7 | &:focus { |
Reorder: move before line 146 |
| scss/_nav.scss | 206:5 | .show > .nav-link { |
Reorder: move before line 146 |
| scss/_nav.scss | 219:5 | > .nav-link, |
Reorder: move before line 138 |
| scss/_nav.scss | 227:5 | > .nav-link, |
Reorder: move before line 138 |
| scss/_nav.scss | 237:5 | .nav-item .nav-link { |
Reorder: move before line 146 |
| scss/_nav.scss | 237:5 | .nav-item .nav-link { |
Reorder: move before line 146 |
| scss/buttons/_button-group.scss | 113:5 | > [class*="btn-"], |
Reorder: move before line 19 |
| scss/buttons/_button-group.scss | 114:5 | > .btn-group { |
Reorder: move before line 52 |
| scss/content/_prose.scss | 84:5 | h1 { |
Reorder: move before line 55 |
| scss/content/_prose.scss | 88:5 | h2 { |
Reorder: move before line 55 |
| scss/content/_prose.scss | 91:5 | h3 { |
Reorder: move before line 55 |
| scss/content/_prose.scss | 94:5 | h4 { |
Reorder: move before line 55 |
| scss/content/_prose.scss | 97:5 | h5 { |
Reorder: move before line 55 |
| scss/content/_prose.scss | 100:5 | h6 { |
Reorder: move before line 55 |
| scss/content/_reboot.scss | 310:3 | code { |
Reorder: move before line 303 |
| scss/content/_reboot.scss | 421:3 | button, |
Reorder: move before line 414 |
| scss/content/_reboot.scss | 432:3 | button, |
Reorder: move before line 414 |
| scss/content/_reboot.scss | 466:3 | button, |
Reorder: move before line 414 |
| scss/forms/_floating-labels.scss | 84:7 | ~ label { |
Reorder: move before line 84 |
| scss/forms/_floating-labels.scss | 84:7 | ~ label { |
Reorder: move before line 84 |
| scss/forms/_floating-labels.scss | 111:7 | ~ label { |
Reorder: move before line 84 |
| scss/forms/_floating-labels.scss | 116:5 | > :disabled ~ label, |
Reorder: move before line 84 |
| scss/forms/_form-range.scss | 57:5 | &::-webkit-slider-thumb { |
Reorder: move before line 43 |
| scss/forms/_form-range.scss | 84:5 | &::-moz-range-thumb { |
Reorder: move before line 47 |
| scss/forms/_input-group.scss | 82:3 | .input-group-lg > .form-control, |
Reorder: move before line 36 |
| scss/forms/_input-group.scss | 83:3 | .input-group-lg > .form-select, |
Reorder: move before line 36 |
| scss/forms/_input-group.scss | 85:3 | .input-group-lg > .btn { |
Reorder: move before line 49 |
| scss/forms/_input-group.scss | 91:3 | .input-group-sm > .form-control, |
Reorder: move before line 36 |
| scss/forms/_input-group.scss | 92:3 | .input-group-sm > .form-select, |
Reorder: move before line 36 |
| scss/forms/_input-group.scss | 94:3 | .input-group-sm > .btn { |
Reorder: move before line 49 |
| scss/forms/_otp-input.scss | 60:5 | &.is-invalid .form-control, |
Reorder: move before line 50 |
| scss/forms/_otp-input.scss | 61:5 | .was-validated &:invalid .form-control { |
Reorder: move before line 54 |
| scss/forms/_otp-input.scss | 64:7 | &:focus { |
Reorder: move before line 54 |
| scss/forms/_otp-input.scss | 76:5 | .form-control { |
Reorder: move before line 50 |
alpha-value-notation
medium impact
auto-fixable
38
Modern CSS uses percentage for alpha: rgb(0 0 0 / 50%) instead of the legacy decimal 0.5. More readable and consistent with other CSS percentage values. Stylelint can auto-fix this — run npx stylelint --fix.
rgba(0, 0, 0, 0.5)
color: rgb(255 255 255 / 0.1)rgb(0 0 0 / 50%)
color: rgb(255 255 255 / 10%)| File | Line | Current code | Suggested fix |
|---|---|---|---|
| scss/_carousel.scss | 107:16 | opacity: 0; |
opacity: 0%; |
| scss/_carousel.scss | 116:16 | opacity: 1; |
opacity: 100%; |
| scss/_carousel.scss | 122:16 | opacity: 0; |
opacity: 0%; |
| scss/_chip.scss | 84:18 | opacity: .9; |
opacity: 90%; |
| scss/_chip.scss | 92:16 | opacity: .65; |
opacity: 65%; |
| scss/_chip.scss | 152:16 | opacity: 1; |
opacity: 100%; |
| scss/_datepicker.scss | 69:14 | opacity: 1; |
opacity: 100%; |
| scss/_datepicker.scss | 90:14 | opacity: 0; |
opacity: 0%; |
| scss/_datepicker.scss | 372:14 | opacity: .5; |
opacity: 50%; |
| scss/_datepicker.scss | 402:14 | opacity: .8; |
opacity: 80%; |
| scss/_dialog.scss | 26:47 | $dialog-backdrop-bg: rgba(0, 0, 0, .5) !default; |
$dialog-backdrop-bg: rgba(0, 0, 0, 50%) !default; |
| scss/_dialog.scss | 81:16 | opacity: 0; |
opacity: 0%; |
| scss/_dialog.scss | 85:18 | opacity: 0; |
opacity: 0%; |
| scss/_dialog.scss | 90:18 | opacity: 1; |
opacity: 100%; |
| scss/_dialog.scss | 93:20 | opacity: 1; |
opacity: 100%; |
| scss/_dropdown.scss | 144:14 | opacity: 1; |
opacity: 100%; |
| scss/_spinner.scss | 71:16 | opacity: 1; |
opacity: 100%; |
| scss/_spinner.scss | 87:14 | opacity: 0; |
opacity: 0%; |
| scss/_toasts.scss | 55:16 | opacity: 0; |
opacity: 0%; |
| scss/_tooltip.scss | 56:14 | opacity: 0; |
opacity: 0%; |
| scss/_transitions.scss | 9:14 | opacity: 0; |
opacity: 0%; |
| scss/buttons/_button.scss | 409:56 | --btn-active-shadow: inset 0 2px 4px rgba(0, 0, 0, .15); |
--btn-active-shadow: inset 0 2px 4px rgba(0, 0, 0, 15%); |
| scss/content/_reboot.scss | 450:16 | opacity: 1; |
opacity: 100%; |
| scss/forms/_chip-input.scss | 69:16 | opacity: 1; |
opacity: 100%; |
| scss/forms/_chip-input.scss | 72:18 | opacity: .65; |
opacity: 65%; |
| scss/forms/_form-control.scss | 95:16 | opacity: 1; |
opacity: 100%; |
| scss/forms/_form-control.scss | 108:16 | opacity: 1; |
opacity: 100%; |
| scss/forms/_form-control.scss | 265:16 | opacity: 1; |
opacity: 100%; |
| scss/forms/_switch.scss | 79:28 | &::before { opacity: .4; } |
&::before { opacity: 40%; } |
| scss/mixins/_backdrop.scss | 9:21 | &.fade { opacity: 0; } |
&.fade { opacity: 0%; } |
| scss/tests/mixins/_box-shadow.test.scss | 100:46 | box-shadow: 0 0 10px rgba(0, 0, 0, .5); |
box-shadow: 0 0 10px rgba(0, 0, 0, 50%); |
| scss/tests/mixins/_box-shadow.test.scss | 116:46 | box-shadow: 0 0 10px rgba(0, 0, 0, .5), 0 0 20px rgba(0, 0, 0, .3); |
box-shadow: 0 0 10px rgba(0, 0, 0, 50%), 0 0 20px rgba(0, 0, 0, .3); |
| scss/tests/mixins/_box-shadow.test.scss | 116:74 | box-shadow: 0 0 10px rgba(0, 0, 0, .5), 0 0 20px rgba(0, 0, 0, .3); |
box-shadow: 0 0 10px rgba(0, 0, 0, .5), 0 0 20px rgba(0, 0, 0, 30%); |
| scss/tests/mixins/_box-shadow.test.scss | 132:46 | box-shadow: 0 0 10px rgba(0, 0, 0, .5); |
box-shadow: 0 0 10px rgba(0, 0, 0, 50%); |
| scss/tests/mixins/_color-contrast.test.scss | 85:43 | $test-background: rgba(118, 118, 118, 1); // Same as #767676 |
$test-background: rgba(100%18, 118, 118, 1); // Same as #767676 |
| scss/tests/utilities/_api.test.scss | 105:20 | opacity: 0; |
opacity: 0%; |
| scss/tests/utilities/_api.test.scss | 109:20 | opacity: .5; |
opacity: 50%; |
| scss/tests/utilities/_api.test.scss | 113:20 | opacity: 1; |
opacity: 100%; |
selector-not-notation
medium impact
auto-fixable
20
CSS Selectors Level 4 allows :not(.a, .b) instead of chaining :not(.a):not(.b). More readable and equivalent. Supported in all modern browsers (Chrome 88+, Firefox 84+, Safari 14+). Stylelint can auto-fix this.
:not(.a):not(.b) { ... }
:not(:first-child):not(:last-child) { ... }:not(.a, .b) { ... }
:not(:first-child, :last-child) { ... }| File | Line | Current code | Suggested fix |
|---|---|---|---|
| scss/_card.scss | 102:24 | &:not(:first-child):not(:last-child) { |
&:not(:first-child, :last-child) { |
| scss/_card.scss | 216:26 | &:not(:first-child):not(:last-child) { |
&:not(:first-child, :last-child) { |
| scss/buttons/_button-group.scss | 58:39 | > [class*="btn-"]:not(:last-child):not(.dropdown-toggle), |
> [class*="btn-"]:not(:last-child, .dropdown-toggle), |
| scss/buttons/_button-group.scss | 124:39 | > [class*="btn-"]:not(:last-child):not(.dropdown-toggle), |
> [class*="btn-"]:not(:last-child, .dropdown-toggle), |
| scss/content/_reboot.scss | 271:16 | a:not([href]):not([class]) { |
a:not([href], [class]) { |
| scss/content/_reboot.scss | 457:28 | [list]:not([type="date"]):not([type="datetime-local"]):not([type="month"]):not([type="week"]):not([type="time"])::-webkit-calendar-picker-indicator { |
[list]:not([type="date"], [type="datetime-local"], [type="month"], [type="week"], [type="time"])::-webkit-calendar-picker-indicator { |
| scss/content/_reboot.scss | 457:57 | [list]:not([type="date"]):not([type="datetime-local"]):not([type="month"]):not([type="week"]):not([type="time"])::-webkit-calendar-picker-indicator { |
[list]:not([type="date"], [type="datetime-local"], [type="month"], [type="week"], [type="time"])::-webkit-calendar-picker-indicator { |
| scss/content/_reboot.scss | 457:77 | [list]:not([type="date"]):not([type="datetime-local"]):not([type="month"]):not([type="week"]):not([type="time"])::-webkit-calendar-picker-indicator { |
[list]:not([type="date"], [type="datetime-local"], [type="month"], [type="week"], [type="time"])::-webkit-calendar-picker-indicator { |
| scss/content/_reboot.scss | 457:96 | [list]:not([type="date"]):not([type="datetime-local"]):not([type="month"]):not([type="week"]):not([type="time"])::-webkit-calendar-picker-indicator { |
[list]:not([type="date"], [type="datetime-local"], [type="month"], [type="week"], [type="time"])::-webkit-calendar-picker-indicator { |
| scss/forms/_form-control.scss | 49:23 | &:not(:disabled):not([readonly]) { |
&:not(:disabled, [readonly]) { |
| scss/forms/_form-control.scss | 128:27 | &:hover:not(:disabled):not([readonly])::file-selector-button { |
&:hover:not(:disabled, [readonly])::file-selector-button { |
| scss/forms/_form-control.scss | 229:21 | &:not(:disabled):not([readonly]) { |
&:not(:disabled, [readonly]) { |
| scss/forms/_input-group.scss | 118:26 | > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), |
> :not(:last-child, .dropdown-toggle, .dropdown-menu, .form-floating), |
| scss/forms/_input-group.scss | 118:48 | > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), |
> :not(:last-child, .dropdown-toggle, .dropdown-menu, .form-floating), |
| scss/forms/_input-group.scss | 118:68 | > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), |
> :not(:last-child, .dropdown-toggle, .dropdown-menu, .form-floating), |
| scss/forms/_input-group.scss | 127:53 | > :nth-last-child(n + 3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), |
> :nth-last-child(n + 3):not(.dropdown-toggle, .dropdown-menu, .form-floating), |
| scss/forms/_input-group.scss | 127:73 | > :nth-last-child(n + 3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), |
> :nth-last-child(n + 3):not(.dropdown-toggle, .dropdown-menu, .form-floating), |
| scss/helpers/_visually-hidden.scss | 5:41 | .visually-hidden-focusable:not(:focus):not(:focus-within) { |
.visually-hidden-focusable:not(:focus, :focus-within) { |
| scss/mixins/_forms.scss | 97:26 | &:not([multiple]):not([size]), |
&:not([multiple], [size]), |
| scss/mixins/_visually-hidden.scss | 35:16 | &:not(:focus):not(:focus-within) { |
&:not(:focus, :focus-within) { |
color-function-notation
medium impact
auto-fixable
7
Modern CSS color functions use space-separated syntax: rgb(255 0 0) instead of rgb(255, 0, 0). The modern syntax also supports the / alpha notation: rgb(0 0 0 / 50%). Stylelint can auto-fix this.
rgb(255, 0, 0)
rgba(0, 0, 0, 0.5)
hsl(120, 100%, 50%)rgb(255 0 0)
rgb(0 0 0 / 50%)
hsl(120 100% 50%)| File | Line | Current code | Suggested fix |
|---|---|---|---|
| scss/_dialog.scss | 26:33 | $dialog-backdrop-bg: rgba(0, 0, 0, .5) !default; |
$dialog-backdrop-bg: rgb(0 0 0 / .5) !default; |
| scss/buttons/_button.scss | 409:42 | --btn-active-shadow: inset 0 2px 4px rgba(0, 0, 0, .15); |
--btn-active-shadow: inset 0 2px 4px rgb(0 0 0 / .15); |
| scss/tests/mixins/_box-shadow.test.scss | 100:32 | box-shadow: 0 0 10px rgba(0, 0, 0, .5); |
box-shadow: 0 0 10px rgb(0 0 0 / .5); |
| scss/tests/mixins/_box-shadow.test.scss | 116:32 | box-shadow: 0 0 10px rgba(0, 0, 0, .5), 0 0 20px rgba(0, 0, 0, .3); |
box-shadow: 0 0 10px rgb(0 0 0 / .5), 0 0 20px rgb(0 0 0 / .3); |
| scss/tests/mixins/_box-shadow.test.scss | 116:60 | box-shadow: 0 0 10px rgba(0, 0, 0, .5), 0 0 20px rgba(0, 0, 0, .3); |
box-shadow: 0 0 10px rgb(0 0 0 / .5), 0 0 20px rgb(0 0 0 / .3); |
| scss/tests/mixins/_box-shadow.test.scss | 132:32 | box-shadow: 0 0 10px rgba(0, 0, 0, .5); |
box-shadow: 0 0 10px rgb(0 0 0 / .5); |
| scss/tests/mixins/_color-contrast.test.scss | 85:23 | $test-background: rgba(118, 118, 118, 1); // Same as #767676 |
$test-background: rgb(118 118 118 / 1); // Same as #767676 |
declaration-block-no-redundant-longhand-properties
low impact
auto-fixable
9
Multiple longhand properties (padding-top, padding-right, etc.) can be combined into a single shorthand (padding). Reduces code size and improves readability. Stylelint can auto-fix this.
padding-top: 1rem;
padding-right: 2rem;
padding-bottom: 1rem;
padding-left: 2rem;padding: 1rem 2rem;| File | Line | Current code | Suggested fix |
|---|---|---|---|
| scss/_datepicker.scss | 232:5 | column-gap: .25rem; |
Use shorthand: gap |
| scss/_datepicker.scss | 316:5 | justify-items: center; |
Use shorthand: place-items |
| scss/_datepicker.scss | 416:5 | border-bottom-left-radius: $border-radius; |
Use shorthand: border-radius |
| scss/_datepicker.scss | 423:5 | border-bottom-left-radius: 0; |
Use shorthand: border-radius |
| scss/_offcanvas.scss | 144:7 | margin-inline-end: calc(-.5 * var(--offcanvas-padding-x)); |
Use shorthand: margin-inline |
| scss/_stepper.scss | 35:7 | left: calc(-50% - var(--stepper-gap)); |
Use shorthand: inset |
| scss/_toasts.scss | 89:7 | margin-inline-end: calc(-.5 * var(--toast-padding-x)); |
Use shorthand: margin-inline |
| scss/tests/mixins/_utilities.test.scss | 138:13 | padding-inline-end: 1rem; |
Use shorthand: padding-inline |
| scss/tests/mixins/_utilities.test.scss | 152:13 | padding-inline-end: 1rem; |
Use shorthand: padding-inline |
scss/no-global-function-names
high impact
manual fix
3
Dart Sass deprecated global functions like adjust-hue(), saturate(), mix(). The modern Sass module system uses @use "sass:color" then namespaced calls like color.adjust(). Bootstrap v6 can adopt this since node-sass is no longer supported. This prevents namespace collisions and makes dependencies explicit.
$color: adjust-hue($primary, 15deg);
$mixed: mix($white, $primary, 80%);@use "sass:color";
$color: color.adjust($primary, $hue: 15deg);
$mixed: color.mix($white, $primary, 80%);| File | Line | Current code | Suggested fix |
|---|---|---|---|
| scss/_functions.scss | 22:20 | } @else if not comparable($prev-num, $num) { |
@use "sass:math" → math.compatible($number1, $number2) |
| scss/tests/mixins/_color-modes.test.scss | 21:29 | --custom-color: #{mix($indigo, $blue, 50%)}; |
@use "sass:color" → color.mix($color1, $color2, $weight) |
| scss/tests/mixins/_color-modes.test.scss | 50:29 | --custom-color: #{mix($indigo, $blue, 50%)}; |
@use "sass:color" → color.mix($color1, $color2, $weight) |
These modernization rules were checked and Bootstrap SCSS is already clean:
media-feature-range-notation — Media queries should use range syntaxshorthand-property-no-redundant-values — Redundant values in shorthand propertiesno-duplicate-selectors — Duplicate selectors in the same fileimport-notation — Import notation should use string syntaxQuick Fix: 74 of 136 warnings are auto-fixable
Run this command to auto-fix what Stylelint can handle:
npx stylelint "scss/**/*.scss" --fix
The remaining 62 warnings (specificity issues, Sass module migration) require manual review.