Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Idea: Split the colors from the rest (placement) to improve theming support #31257

Closed
Carl-Hugo opened this issue Jul 7, 2020 · 3 comments
Closed

Comments

@Carl-Hugo
Copy link

I created a dark theme for Bootstrap 4, and I noticed that you are aiming at v6 (#27514) to support a dark version of Bootstrap officially, which is fantastic.

Based on that experience, what I think would be very helpful, would be to extract the colors of elements from the rest of the CSS. This way, we would have a "Core" SCSS and a "Colors" SCSS for each component of Bootstrap. Doing that would allow loading the Core CSS once and as many themes (Colors variants) as we want to.

The "Core" and "Colors" could also be divided into modules, as suggested in #28900. One idea does not block the other.

The Core should not contain any color (including shadows, outline, etc.), while the Colors should only contain instructions that color elements. The color themselves should remain variables (or one variables file per module, in case of modules), so we keep the same control as of now (using variables), with the possibility of more (theming).

For example, by applying this principle, we could generate one CSS that contains light and dark themes as easy as:

// Core
@import "functions";
@import "mixins";
// ...
@import "forms-core";
@import "buttons-core";
// ...
// Light theme (colors)
@media (prefers-color-scheme: light) {
    @import "variables-light";
    // ...
    @import "forms-colors";
    @import "buttons-colors";
    // ...
}
// Dark theme (colors)
@media (prefers-color-scheme: dark) {
    @import "variables-dark";
    // ...
    @import "forms-colors";
    @import "buttons-colors";
    // ...
}

And even add a third "theme" like:

// A random theme (colors)
@media (prefers-color-scheme: highcontrast) {
    @import "variables-highcontrast";
    // ...
    @import "forms-colors";
    @import "buttons-colors";
    // ...
}

The use of prefers-color-scheme is optional and could be replaced by any other technique that is the most appropriate for each individual use-case. We could also split the CSS into multiple files and whatnot.

While at it, another thing that is hard to adjust is the inline modification of colors instead of using variables (like inlining darken(...) and other similar functions. It would be great not to have any of that anywhere as well 😉

Bottom line: I think that by doing this, it would make it way easier for the community to create themes and for you in the future to maintain both a light and a dark theme.

@herbalite
Copy link

herbalite commented Jul 9, 2020

I am working on dark/light theming too, served in one CSS file, for the plain reason, that I work with: "whatever can be done with CSS should be done with CSS". I decided to stick with CSS variables for reasons that should become evident later on, plus the scripting part of Bootstrap works still the same way if I just fiddle with the CSS output.

Otherwise, I did run into some of the same issues you mentioned.

I'd like to add some of my observations while exploring this issue.

How to tackle the default mode? For the website I am working on, it is sufficient that light and default is essentially the same.

What happens with browsers that don't support prefers-color-scheme? Which scheme will be the one used? In my immediate case, I'm upgrading an existing site, which was light mode. Hence I ensure that light mode is the same as the "default" mode.
How to build that fallback scheme? I did it by leaving out the prefers-color-scheme: light media path and thus apply a default scheme. Here's a question. Does a fallback scheme work with your approach? How should I choose a default scheme other than light?

Does the color separation as you suggest work in my case? Yes and no. I end up with tons of extra CSS variables, which I don't like, because it adds extra noise.

Long story short, I have to come to see the SASS variable $theme-colors in Bootstrap to mean $ui-state-colors, empahsis on state, which helps me a lot. What I did so far was to create a $color-scheme, whose values feed into Bootstraps $theme-colors. The color scheme itself is HSL based, which makes it much easier to understand how the colors were derived (from one color in this case)

  $scheme-luminosity-start: 2% !default;
  $scheme-luminosity-interval: 9.6% !default;

  $color-scheme-dark: () !default;
  $color-scheme-dark: map-merge(
    (
      "lum10": hsl( 205, 100%, 2% ),
      "lum9": hsl( 205, 100%, 11.6% ),
      "lum8": hsl( 205, 100%, 21.2% ),
      "lum7": hsl( 205, 100%, 30.8% ),
      "lum6": hsl( 205, 100%, 40.4% ),
      "lum5": hsl( 205, 100%, 50% ),
      "lum4": hsl( 205, 100%, 59.6% ),
      "lum3": hsl( 205, 100%, 69.2% ),
      "lum2": hsl( 205, 100%, 78.8% ),
      "lum1": hsl( 205, 100%, 88.4% ),
      "lum0": hsl( 205, 100%, 98% ),
  
      "sat90": hsl( 205, 90%, 50% ),
      "sat80": hsl( 205, 80%, 50% ),
      "sat70": hsl( 205, 70%, 50% ),
      "sat60": hsl( 205, 60%, 50% ),
      "sat50": hsl( 205, 50%, 50% ),
      "sat40": hsl( 205, 40%, 50% ),
      "sat30": hsl( 205, 30%, 50% ),
      "sat20": hsl( 205, 20%, 50% ),
      "sat10": hsl( 205, 10%, 50% ),
      "sat0": hsl( 205, 0%, 50% ),
  
      "warn": hsl( 325, 100%, 50% ), // 120deg
      "success": hsl( 115, 100%, 50% ) // -120deg
    ),
    $color-scheme-dark );

  $primary-dark-scheme:        map-get( $color-scheme-dark, 'lum4' ); 
  $secondary-dark-scheme:    map-get( $color-scheme-dark, 'sat10' ); 
  $success-dark-scheme:        map-get( $color-scheme-dark, 'success'); 
  $info-dark-scheme:              map-get( $color-scheme-dark, 'sat40' ); 
  $warning-dark-scheme:       map-get( $color-scheme-dark, 'lum7' ); 
  $danger-dark-scheme:        map-get( $color-scheme-dark, 'warn' ); 
  $light-dark-scheme:            map-get( $color-scheme-dark, 'lum1' ); 
  $dark-dark-scheme:            map-get( $color-scheme-dark, 'lum9' );

  $theme-colors-dark: () !default;
  $theme-colors-dark: map-merge(
    (
      "primary":    $primary-dark-scheme,  
      "secondary":  $secondary-dark-scheme,
      "success":    $success-dark-scheme,
      "info":       $info-dark-scheme,
      "warning":    $warning-dark-scheme,
      "danger":     $danger-dark-scheme,
      "light":      $light-dark-scheme,
      "dark":       $dark-dark-scheme,
    ),
    $theme-colors-dark
  );

Some of these values will be written out as CSS Vars e.g, in their respective @media (prefers-color-scheme: ... {} section, except for the default/light case.

The nitty gritty is as you already mentioned with these darken(..), lighten(..), mix(..) colors.

I have an idea that I am working on, and the idea is that all the colors are collected as states, for all the themes, light/dark, and so on. One state could be named primary-l-5, secondary-l-5 ( meaning secondary color, lighten, 5%) and in the resulting CSS using a mixin might look like this.

@mixin focusblabla ($state ) {
  .#{$state}:focus {
    background-color: var(--#{state}-l-5); 
  }
}

It also needs a function to compare map values, not just map keys, but that is no big deal for files that get created once as CSS file. For dynamic purposes it be nice if SASS added map:has-value ... But this is getting off track.

The net result is as few CSS vars as possible (although most of them will be doubled for the different themes). Also as a result in the browser developer console, any change to a CSS var will be reflected for every reference. Wherever focus values for the different components are in use (often having the same color), they end up having just one CSS variable, change that for a short demonstration, et voila! (Hence I'm not so excited about local CSS vars being used in Bootstrap5).

One neat side effect is that this can also be extended to other values than colors. So it can be used to create a theme with extra large font-size, or whatever else is required.

@Carl-Hugo
Copy link
Author

How to tackle the default mode?

I think that flexibility will be very important here because different people/projects will have different needs.

@vinorodrigues analyzed that (amongst other things) as well in his: The Definitive¹ Guide to Dark Mode and Bootstrap 4.

Does the color separation as you suggest work in my case? Yes and no. I end up with tons of extra CSS variables, which I don't like, because it adds extra noise.

I'm only talking about SCSS variables here, so CSS variables should not have any impact on this idea of separating colors and positioning at the SCSS-level. The goal behind the idea is that one can compose a Bootstrap stylesheet as he wants, easily.

To be honest, I did not look at Bootstrap 5 before writing this but referred to Bootstrap 4, and there seem to be many CSS variables everywhere now, indeed. However, CSS variables are a different concern and whether or not to embed them everywhere should be made in a different discussion.

background-color: var(--#{state}-l-5);

This seems to have nothing to do with this idea, but embedding -l-5 into your SCSS files does not seem different than using darken(...) and others functions like that because you are directly coupling your mixin with your color-scheme, cutting out flexibility. In this case, you even seem to be mixing SCSS and CSS variables; I don't see any added value from doing that (maybe I'm missing something).

map:has-value

Not sure why you'd need that, but map-get($map, $key) should do the trick in most cases since null will evaluate to false and other values to true (unless the value is a falsy value).


Now that I think about it, it would most likely require an additional variables-common.scss or variables-base.scss that contains shared structure variables, or even default colors.

Here is an example of what I think should go in each file (missing from my original post):

// variables-common.scss
$spacer: 1rem !default;
$table-cell-vertical-align: top !default;

// variables-light.scss
$table-color: #212529;
$table-border-color: #dee2e6;

// variables-dark.scss
$table-color: #d3d3d3;
$table-border-color: #343a40;

// tables-core.scss
.table {
  width: 100%;
  margin-bottom: $spacer;
  vertical-align: $table-cell-vertical-align;
  // ...
}

// tables-colors.scss
.table {
  // ...
  color: $table-color;
  border-color: $table-border-color;
  // ...
}

Then, we could bootstrap-light-and-dark.scss:

// ...
@import "variables-common.scss";
// ...
@import "tables-core";
// ...
// Light theme (colors)
@media (prefers-color-scheme: light) {
    @import "variables-light";
    // ...
    @import "tables-colors";
    // ...
}
// Dark theme (colors)
@media (prefers-color-scheme: dark) {
    @import "variables-dark";
    // ...
    @import "tables-colors";
    // ...
}

Or also bootstrap-light.scss:

// ...
@import "variables-common.scss";
// ...
@import "tables-core";
// ...
@import "variables-light";
// ...
@import "tables-colors";
// ...

Or bootstrap-dark.scss:

// ...
@import "variables-common.scss";
// ...
@import "tables-core";
// ...
@import "variables-dark";
// ...
@import "tables-colors";
// ...

And so on: "endless" composition options.

@mdo mdo added the v6 label Jan 13, 2021
@mdo
Copy link
Member

mdo commented Jan 13, 2021

Closing as a won't fix given v5's beta status and no breaking changes. Flagging for v6 consideration though!

@mdo mdo closed this as completed Jan 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants