Consider dynamic theming support
Created by: yugabe
Prerequisites
-
I have searched for duplicate or closed feature requests -
I have read the contributing guidelines
Proposal
Consider adding support to virtually any number of themes, or even theming dynamically.
The theming support introduced in 5.3.0-alpha1 only made it harder to customize or extend the color palette. This proposal would make it arguably a lot easier to add custom themes, extend the color palette, shrink the CSS output by trimming unused colors from the palette and make both the user and framework codes easier to maintain.
Motivation and context
In the end, the current $primary
/$secondary
/$info
etc. variables are leftovers of a time before CSS variables (custom properties) were made available to use. Consider defining a rule as follows, either by user code or in Bootstrap's source itself:
// Some user code:
.some-class {
color: $primary;
}
This approach is problematic, because if $primary
refers to a raw value (like it does currently, which is $blue
, that resolves to #0d6efd
by default), the .some-class
rule above won't respond correctly to theming changes (https://github.com/twbs/bootstrap/issues/37976). This also happens if the user changes the default value to whatever else: in the end, the raw color value will be compiled into the raw CSS. A much more preferable way would be if both user code and framework code would refer to the CSS variable, like so:
// Some user code:
.some-class {
color: var(--#{$prefix}primary);
}
Although, this interpolation is very prone to breaking, because nothing guarantees that the given hard-coded variable name will be correct. The user might simply make a typo and it would be very hard to debug an issue like this in some cases. A simple solution would be something like this:
// Some user code:
$primary-var: var(--#{$prefix}primary);
.some-class {
color: $primary-var;
}
It is very much also possible that making the theme colors (like $primary
) available would serve no other purpose than for users to incorrectly use the raw, default, initial values of. Thus, it makes sense to fully repurpose the SCSS variables so that they point to the CSS variables instead of the raw values.
// Proposed change to Bootstrap variable declaration:
$primary: var(--#{$prefix}primary); // notice no '!default', as there is no reason to change this variable at all
This way, user code would, in most cases, automatically follow theming changes as well, because they wouldn't have to change from the initial example above:
// Some user code:
.some-class {
color: $primary;
}
A breaking change though would be how the default values would need to be set for the users for themes:
// Proposed change to Bootstrap variable declaration:
:root {
#{$primary}: $blue;
}
The above would set the CSS variable corresponding to the "old" $primary
SCSS variable to $blue
, its current default value. The problem with this approach is that there is no way to set these as "default" values, as because technically this isn't an SCSS variable declaration, and !default
is SCSS's syntax - obviously not supported in compiled CSS. Thus, the user would have to manually define the variable value themselves at :root
.
A more elegant way to define a full themeset could look as follows:
// Proposed change to Bootstrap theme declaration:
$themes: (
"light": (
"body-bg": $white,
"body-color": $gray-900,
"primary": $blue,
"success": $green,
"info": $cyan,
"warning": $yellow,
"danger": $red
),
"dark": (
"body-bg": $gray-900,
"body-color": $gray-500,
"primary": $blue-300,
"success": $green-300,
"info": $cyan-300,
"warning": $yellow-300,
"danger": $red-300
)
) !default;
Notice that there are no more variable declarations at all (no $primary
, nor $primary-var
). That's because the above 5 values in the themes can be considered fully agnostic of context. The user can:
- name the colors however they'd like,
- can add or remove any number of them,
- can simply add more themes by providing a theme key with a map structured as all other maps in the
$themes
map.
A user can then simply add a theme by doing something like:
$themes: (
"light": (
"body-bg": $gray-100,
"body-color": $gray-900,
"accent-1": #9ffa5c,
"accent-2": $green-700,
"error": $red
),
"dark": (
"body-bg": $black,
"body-color": $gray-200,
"accent-1": tint-color(#9ffa5c, 60%),
"accent-2": $green-300,
"error": $red-200
),
"blue": (
"body-bg": $blue-100,
"body-color": $gray-900,
"accent-1": $blue-600,
"accent-2": $cyan-300,
"error": $purple-200
)
)
Shading, borders, emphasis etc. can all be calculated based on the given values as well, and can be applied to themable components at the component level. Overriding any variable would be done in CSS variables instead. Notice that in the last example, body-bg
and body-color
were required colors, and "secondary", "light" and "dark" were removed. This is because $secondary
(and tertiary) were repurposed in the most recent release, so they are essentially shades of gray or shades/tints of a given them color (-subtle
).