Real-world CSS custom props

A presentation at Web Directions Hover 2022 in April 2022 in Sydney NSW, Australia by Ben Buchanan (200ok)

Slide 1

Slide 1

Real world CSS custom properties Ben Buchanan weblog.200ok.com.au

Slide 2

Slide 2

Slide 3

Slide 3

Slide 4

Slide 4

Slide 5

Slide 5

Slide 6

Slide 6

Slide 7

Slide 7

SCSS for style API & customisation Imposes stack choice on consumer :( Precompiled modifications only :-/ Provides error handling :)

Slide 8

Slide 8

SCSS for themes

Slide 9

Slide 9

Slide 10

Slide 10

SCSS for customisation

Slide 11

Slide 11

I wanted a better way Native portability Runtime features Easier customisation

Slide 12

Slide 12

Qbit private API HTML CSS ES6 Qbit Public API Templates SCSS variables CSS custom props JSON design tokens

Slide 13

Slide 13

Implementation: CSS custom prop themes :root { /* common variables */ } [data-qtheme=”blue”] {} [data-qtheme=”green”] {}

Slide 14

Slide 14

CSS variable payload

Slide 15

Slide 15

CSS variable payload

Slide 16

Slide 16

Custom theme

Slide 17

Slide 17

So how is it going?

Slide 18

Slide 18

Slide 19

Slide 19

Slide 20

Slide 20

Slide 21

Slide 21

Targeted customisation [data-qtheme=”entirecustomtheme”] { } .element-override {} .contextual-override .element {} #instance-override {}

Slide 22

Slide 22

Terse customisation .element-override { property: value; .subelement1-override { property: value; } } .element-override { —prop: val; }

Slide 23

Slide 23

IE11 was fine postcss([ postCSSCustomProperties({ importFrom: ‘./dist/css-variables.css’ }), postCSSCalc() ])) .selector { margin-top: 40px; margin-top: calc(var(—space-l) * 1.25); }

Slide 24

Slide 24

I quickly missed SCSS errors Error: Undefined variable: “$button-border-widht”. on line 31 of path/to/button.scss from line 8 of path/to/qbit-all.scss >> -width: #{$button-border-widht}; ———————————————-^

Slide 25

Slide 25

Non-draconian error handling… .selector { color: var(—colour-that-does-not-exist); } …means this does not throw an error.

Slide 26

Slide 26

Stylelint to the rescue! “plugins”: [ “stylelint-scss”, “stylelint-value-no-unknown-custom-properties” ] path/to/component.css 85:18 ✖ Unexpected custom property “—colour-hoover” inside declaration “color”.

Slide 27

Slide 27

A shout out to calc() —header-height: calc(var(—grid-unit) * 12);

Slide 28

Slide 28

So what went….not so well?

Slide 29

Slide 29

SCSS habits $variant1: value; $variant2: value; // just do this in every file! it’s all good! @import “_import-vars-everywhere.scss”; .elementvariant1 { color: $variant1; } .elementvariant2 { color: $variant2; }

Slide 30

Slide 30

All aboard the bloat boat @import “_repeat-your-css-vars-everywhere.scss”; Be sure to only import your CSS vars once 😳

Slide 31

Slide 31

Bloat fixed, debugging broken path/to/component.css 85:18 ✖ Unexpected custom property “—valid-property” inside declaration “color”. Browser: ✔ Build: ✖

Slide 32

Slide 32

Perfect timing! importFrom: ‘./dist/css-variables.css’

Slide 33

Slide 33

A bad habit amplified $colour-text: value; $colour-link: value;

Slide 34

Slide 34

Don’t forget the namespace —qbit-colour-text: value; —qbit-colour-link: value;

Slide 35

Slide 35

A fundamental CSS habit .element {} .elementvariant1 {} .elementvariant2 {}

Slide 36

Slide 36

A fundamental CSS habit flipped .variant1 { —text-colour: #000; } .variant2 { —text-colour: #222; } .element { color: var(—text-colour); }

Slide 37

Slide 37

SCSS does this for themes theme1.scss $colour: colour1; theme2.scss $colour: colour2; component.scss .element { color: $colour; }

Slide 38

Slide 38

…but not variants theme1.scss .element { color: $colour; } .elementvariant1 { color: $anothercolour; } .elementvariant2 { color: $someothercolour; }

Slide 39

Slide 39

Configuration-driven CSS .variant1 { —text-colour: #000; } .variant2 { —text-colour: #222; } .element { color: var(—text-colour); }

Slide 40

Slide 40

SCSS habit: component vars $theme-var: value; $component-proxy-var: $theme-var; .component { color: $component-proxy-var; }

Slide 41

Slide 41

They really aren’t variables :root .selector .scope .selector #hammer { { { { —cp: —cp: —cp: —cp: value1; value2; value3; value4; } } } } The normal rules of CSS determine which will apply.

Slide 42

Slide 42

SCSS habit: component vars Beware of impacts to your style API. .custom-theme { —theme-var: value; —component-var: value; }

Slide 43

Slide 43

SCSS habit: component vars :root { —theme-var: value; } .component { —component-var: var(—theme-var); color: var(—component-var); }

Slide 44

Slide 44

In summary…

Slide 45

Slide 45

In summary… CSS custom properties are awesome and we’re not looking back!

Slide 46

Slide 46

Key benefits of CSS custom properties Zero build Easy customisation Runtime power Portability

Slide 47

Slide 47

Portability wins Anything that can produce HTML can use Qbit’s CSS API.

Slide 48

Slide 48

Embracing Custom Properties Build new habits They aren’t variables Use the power of specificity and the cascade Think config-driven style

Slide 49

Slide 49

Use native CSS! -finweblog.200ok.com.au