Shopify / slate

Slate is a toolkit for developing Shopify themes. It's designed to assist your workflow and speed up the process of developing, testing, and deploying themes.
https://shopify.github.io/slate
MIT License
1.28k stars 364 forks source link

CSS Custom Variable - SCSS operations and functions #503

Closed jonathanmoore closed 6 years ago

jonathanmoore commented 6 years ago

Problem

With the new workflow of defining css-variables.liquid that are then imported into a theme's SCSS file, it currently is not possible to utilize any SCSS color functions. Hypothetically this would apply to all other SCSS functions/mixins that need to be applied to values from the theme editor.

snippets/css-variables.liquid

<style>
  :root {
    --color-body-text: {{ settings.color_body_text }};
    --color-main-background: {{ settings.color_main_bg }};
  }
</style>

styles/theme.scss

$color-body: var(--color-main-background);
$color-body-text: var(--color-body-text);

// SCSS blend of colors
$color-body-blend: mix($color-body, $color-body-text, 50%);

Error:

Error in ./assets/styles/theme.scss) Module build failed: $color-body-blend: mix($color-body, $color-body-text, 50%); ^ Argument $color-1 of mix($color-1, $color-2, $weight: 50%) must be a color

More Information

Obviously this one basic example could be rewritten to use basic Liquid color filters in the css-variables file. More complex color functionality like checking for brightness on a background color and setting an a11y contrast compatible text color would result in very complex liquid

t-kelly commented 6 years ago

@jonathanmoore you're correct in that the replacement solution would be to use Liquid colour filters:

<style>
  :root {
    --color-body-text: {{ settings.color_body_text | color_modify: 'red', 255 }};
    --color-main-background: {{ settings.color_main_bg | color_lighten: 30 }};
  }
</style>

We could continue to add to this list of available filters. Besides a colour contrast filter, are there any other filters that would be useful for you to replace SCSS functionality?

bertiful commented 6 years ago

This is a larger issue with using custom properties with SCSS.

There is currently no way, from what I see, to use custom properties inside of SCSS functions/mixins. I learned this the hard way, and it now makes sense why it doesn't work as expected. An example I ran into was the vertical rhythm implementation (using the Plumber Sass library): https://github.com/Shopify/starter-theme/issues/1. I'm going to keep thinking about this 🤔.

jonathanmoore commented 6 years ago

I am completely stumped on a solution to fit basic and common SCSS functionality into the new concept of CSS variables. Here is a scenario where our current and future themes that will not work with the new version of Slate:

@function get-color-accent-text($color) {
  @if (lightness($color) > 50) {
    @return #000000;
  } @else {
    @return #ffffff;
  }
}

$color-accent: {{ settings.color_accent }};
$color-accent-text: get-color-accent-text($color-accent);

In working with Slate 1.0.0 beta there does not seem to be a way to pull off any {% if %} logic, and we are limited to long chains of filters. Here is what I attempted, however on deploy the assign values from snippets/css-variables.liquid are not available in the generated .css.liquid file.

{%- assign color_accent_lightness = settings.color_accent | color_brightness | divided_by: 255 | times: 100 -%}
{%- if color_accent_lightness > 50 -%}
  {%- assign color_accent_text = '#000000' -%}
{%- else -%}
  {%- assign color_accent_text = '#FFFFFF' -%}
{%- endif -%}
--color-accent-text: {{ color_accent_text }};
jonathanmoore commented 6 years ago

To take it even further, we have plans to improve the contrast for accessibility by using the YIQ color space which provides better results over brightness. http://dannyruchtie.com/color-contrast-calculator-with-yiq/ https://medium.com/@MikeKellyWeb/calculating-color-contrast-with-sass-eff39ef23f96 image

Perhaps this would be a good example for a new liquid color filter...

Input: #7AB55C {{ '#7AB55C' | color_contrast }} Output: #000000

Input: #F33D6A with manually overwriting the dark and light value {{ '#F33D6A' | color_contrast: dark: '#222222', light: '#DDDDDD' }} Output: #DDDDDD

I still there was a way to preserve some basic SCSS that could be rendered by Shopify's servers. Being able to run the theme's dynamic settings through logic is quite helpful.

jonathanmoore commented 6 years ago

More information to expand this issue beyond color example...

When relying on CSS variables to locally compile SCSS files for development or production, Sass will fail to build if you apply any operations to Sass variables assigned by CSS variables.

Replication steps

  1. Add a settings_schema.json setting for a base font size, base_font_size, with values ranging 15px-20px.
  2. Assign CSS value in `css-variables.liquid
    <style>
    :root {
    --base-font-size: {{ settings.base_font_size }};
    }
    </style>
  3. In one of the SCSS files, applying any type of operation to a CSS variable will result in the build error.
    $base-font-size: var(--base-font-size);
    body { font-size: $baseFontSize; }
    h1 { font-size: $baseFontSize * 2; }
$base-font-size: var(--base-font-size);
@function em($target, $context: $base-font-size) {
  @return ($target / $context) * 1em;
}
body { font-size: $base-font-size; }
h1 { font-size: em(28px); }
  1. Example error
    ERROR in ./assets/styles/theme.scss
    Module build failed: ModuleBuildError: Module build failed:
    font-size: $base-font-size * 2;
            ^
      Undefined operation: "var(--base-font-size) times 2".

More Information

Researching into the use of CSS variables along with Sass, it seems like you would have to use calc(). I don't know if there is a good workaround that isn't going to feel like a big hack. Overall this seems like it would have a big negative impact on the usefulness of Sass.

Trying to fit a complex theme into the current CSS vars approach would result in several hundred lines of code to chain together liquid assigns and filters, css vars and scss variables.

t-kelly commented 6 years ago

Update - we shipped some more Liquid color filters!

jonathanmoore commented 6 years ago

Now understanding where Slate is heading with CSS Vars, I have finally reworked our theme code to fit into the concept of Slate. I just wanted to follow up and document some details in case anyone else runs into similar challenges.

In general Slate will not allow for SCSS processing or manipulation of theme settings values. This is something we have taken advantage of in the past, so I had to approach processing colors, font sizes, etc from a different direction. Now we are using Liquid to manipulate colors or sizes and pass it along directly to the CSS vars. I see this approach as having two advantages:

  1. As @t-kelly mentioned above, Shopify can fairly easily (and quickly) roll out new liquid filters as needed
  2. Using CSS Vars to pass values along to CSS is the way that things are heading. Once IE11 is no longer a requirement this opens the door to liquid-free CSS.

Here are the two ways I'm now solving the examples detailed above...

Calculating Accent Text Color (YIQ color profile)

{%- assign color_light_contrast = '#ffffff' -%}
{%- assign color_dark_contrast = '#000000' -%}

{%- comment -%}
  Color - Primary Accent Text
{%- endcomment -%}
{%- assign color_r = color_primary_accent | color_extract: 'red' -%}
{%- assign color_g = color_primary_accent | color_extract: 'green' -%}
{%- assign color_b = color_primary_accent | color_extract: 'blue' -%}
{%- assign color_y_r = color_r | times: 0.299 -%}
{%- assign color_y_g = color_g | times: 0.587 -%}
{%- assign color_y_b = color_b | times: 0.114 -%}
{%- assign color_y = color_y_r | plus: color_y_g | plus: color_y_b | divided_by: 255 -%}
{%- if color_y >= 0.5 -%}
  {%- assign color_primary_accent_text = color_light_contrast -%}
{%- else -%}
  {%- assign color_primary_accent_text = color_light_contrast -%}
{%- endif -%}

Font Size Variations

The theme's settings allow for a choice of 16px to 20px as the base font size (body size), and then we are using REM or EM throughout the style to alter the size for various use. However, we wanted to offer a theme setting where merchants could control how large or small heading font sizes are. Rather than assigning pixel values for the headings, the values range from 0.75 to 1.5. Then the theme's CSS will use calc() to multiply the default heading REM size by the setting value.

<!-- css-variables.liquid -->
<style>
  :root {
    --typography-body-size: {{ setting.typography_body_size }}; // 16px to 20px
    --typography-heading-size: {{ setting.typography_heading_size }}; // 0.75 to 1.5
    --typography-base-line-height: 1.5rem;
    --
  }
</style>
// theme.scss
$typography-body-size: val(--typography-body-size);
$typography-heading-size: val(--typography-heading-size);
$typography-base-line-height: val(--typography-base-line-height);

@mixin calc-font-size(
  $font-size: $typography-body-size,
  $line-height: $typography-base-line-height,
  $size: 1
) {
  font-size: calc(#{$font-size} * #{$size});
  line-height: calc(#{$line-height} * #{$size});
}

html {
  font-size: $typography-body-size;
}
body {
  font-size: 1rem;
  line-height: $typography-base-line-height;
}
h1 {
  @include calc-font-size(2.5rem, 3rem, $typography-heading-size);
}
h2 {
  @include calc-font-size(2rem, 2.5rem, $typography-heading-size);
}
georgebutter commented 6 years ago

are there any other filters that would be useful for you to replace SCSS functionality?

Using css vars you cannot modify opacity like you previously could rgba($colorHighlight, 0.35). Would this be something that would require a liquid filter now? Or is there a more obvious approach that I am missing?

I'm not sure I understand the full benefits of using css vars as opposed to scss vars.

jonathanmoore commented 6 years ago

@ButsAndCats There is a liquid filter that would take care of that example... {{ settings.color_highlight | color_modify: 'alpha', 0.35 }}

Although the current version of Slate still is missing the ability to pass along usable assigned liquid variables. Solution in the works https://github.com/Shopify/slate/issues/541

montalvomiguelo commented 6 years ago

Here is another example for adaptive colors by using color filters

src/snippets/liquid-variables.liquid

{%- capture color_btn_primary_focus -%}
  {%- assign color = settings.color_button  -%}
  {%- assign color_brightness = color | color_brightness -%}
  {%- if color_brightness <= 26 -%}
    {{- color | color_lighten: 25 -}}
  {%- elsif color_brightness <= 64 -%}
    {{- color | color_lighten: 15 -}}
  {%- else -%}
    {{- color | color_darken: 10 -}}
  {%- endif -%}
{% endcapture %}

src/snippets/css-variables.liquid

{%- include 'liquid-variables' -%}

<style>
  :root {
    --color-btn-primary-focus: {{ color_btn_primary_focus }};
  }
</style>

https://github.com/montalvomiguelo/starter-debut/blob/liquid-variables/src/snippets/liquid-variables.liquid

lock[bot] commented 5 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.