Bootstrap v6 — Stylelint SCSS Modernization Report

CSS/SCSS patterns that can be modernized for v6

136 Warnings
6 Rules
34 Files affected
106 Files scanned
Descending specificity

59

no-descending-specificity

Alpha values should use percentage notation

38

alpha-value-notation

:not() should use modern list syntax

20

selector-not-notation

Redundant longhand properties

9

declaration-block-no-redundant-longhand-properties

Color functions should use modern syntax

7

color-function-notation

SCSS global functions

3

scss/no-global-function-names

Specificity & Selectors

Descending specificity — selector ordering issues

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 →
Before (current)
.card-body:not(:first-child) { ... }
.card-body { padding: 1rem; }  /* won't override above */
After (modern)
.card-body { padding: 1rem; }  /* general first */
.card-body:not(:first-child) { ... }  /* specific after */
FileLineCurrent codeSuggested 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

Modern CSS Syntax

Alpha values should use percentage notation

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.

MDN Documentation →
Before (current)
rgba(0, 0, 0, 0.5)
color: rgb(255 255 255 / 0.1)
After (modern)
rgb(0 0 0 / 50%)
color: rgb(255 255 255 / 10%)
FileLineCurrent codeSuggested 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%;

:not() should use modern list syntax

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.

MDN Documentation →
Before (current)
:not(.a):not(.b) { ... }
:not(:first-child):not(:last-child) { ... }
After (modern)
:not(.a, .b) { ... }
:not(:first-child, :last-child) { ... }
FileLineCurrent codeSuggested 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 functions should use modern syntax

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.

MDN Documentation →
Before (current)
rgb(255, 0, 0)
rgba(0, 0, 0, 0.5)
hsl(120, 100%, 50%)
After (modern)
rgb(255 0 0)
rgb(0 0 0 / 50%)
hsl(120 100% 50%)
FileLineCurrent codeSuggested 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

Code Optimization

Redundant longhand properties — use shorthand

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.

MDN Documentation →
Before (current)
padding-top: 1rem;
padding-right: 2rem;
padding-bottom: 1rem;
padding-left: 2rem;
After (modern)
padding: 1rem 2rem;
FileLineCurrent codeSuggested 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

Sass Modernization

SCSS global functions — use @use modules

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.

Sass Documentation →
Before (current)
$color: adjust-hue($primary, 15deg);
$mixed: mix($white, $primary, 80%);
After (modern)
@use "sass:color";
$color: color.adjust($primary, $hue: 15deg);
$mixed: color.mix($white, $primary, 80%);
FileLineCurrent codeSuggested 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)

Passed Checks (0 warnings)

These modernization rules were checked and Bootstrap SCSS is already clean:

Quick 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.