slides.oddbird.net/workshops/23-smashing-de/2-values/

Value Resolution & Custom Properties

slides.oddbird.net/workshops/23-smashing-de/2-values/

@ Smashing Conf Freiburg

Global Design Tokens

brand colors, sizes, fonts, etc…

hsl(322, 92%, 24%)

// origin
$best-color: hsl(322, 92%, 24%);

// meaning
$action: $best-color;

// usage
$link: $action;
$button: $action;

Don’t Do Anything

Until we apply them

button {
background-color: $button;
}

-webkit-property
-moz-property
--property

Define On Elements

// global
$best-color: hsl(322, 92%, 24%);
$action: $best-color;
$button: $action;
html {
--best-color: hsl(322, 92%, 24%);
--action: var(--best-color);
--button: var(--action);
}
:root {
--brand-color: hsl(330, 100%, 45%);
--action: var(--brand-color);
}

Any Document Root

Like <svg> or <xml> or <html>

Inherit By Default

$best-color: hsl(329.8 67.7% 57.5%);

html {
--action: #{$best-color};
}
$colors: (
'brand-blue': hsl(195, 52%, 31%),
'brand-orange': hsl(24, 100%, 62%),
'brand-pink': hsl(322, 92%, 24%),
);

html {
@for $name, $value in $colors {
--#{$name}: #{$value};
}
}

Preprocessor are… Compiled (Server-Side)

button {
background-color: hsl(322, 92%, 24%);
}

Sass Variables are Static at Compilation

Responds to Devices

Responds to Browsers

Responds to User Preferences

Responds to Interactions

Responds to HTML Context

CSS Variables Need a New Mental Model

"CSS Custom Properties

…for Cascading Variables Module Level 1"

Custom Properties

Properties that we define

The Cascade Filters Targeted Values

when there are conflicts

p { color: black; }
p.error { color: red; }

Change values Based on State, Class, &c

Then Inheritance Fills in Missing Values

based on nearest target ancestor

html { color: green; }
p { /* color inherits */ }

Custom properties Capture a Value

Somewhere in the cascade

html { --action: red; }
main { --action: green; }
aside { --action: blue; }

And then… That Value Inherits

Through the DOM

button { background: var(--action); }
button {
background: blue
}

.my-context button {
background: rebeccapurple;
}
button {
background: var(--btn-color, blue);
}

.my-context {
--btn-color: red;
}

Inheritance Rewards Proximity

Like “scoped styles”

Reset Inheritance With Explicit Selectors

Declare Locally

[data-button='go'] {
--btn-color: green;
}

Declare Universally

* {
--grid-area: main;
}

Combine Both

[data-grid] > * {
--grid-area: main;
}

like JS Undefined Initial Guaranteed Invalid

button {
background: var(--btn-color, teal);
}

var( --undefined, fallback )

Allows List Values

var(--my-font, Baskerville, Georgia, serif)

Nested Fallbacks

button {
background: var(--btn-color, var(--action, teal));
}

Font Stacks

font-family: Consolas, Menlo, 'Courier New', monospace;

Browser Fallbacks

button {
background: teal; /* old browser */
/* empty variables */
background: var(--btn-color, var(--action, teal));
}

Browser Fallbacks

html { background: red; }

@supports (--css: variables) {
html { background: green; }
}
  • [attr]Presence (even if empty)
  • [attr="..."]Exact match
  • [attr*="..."]Any match
  • [attr~="..."]Space-delimited (like classes)
  • [attr|="..."]Hyphen-delimited
  • [attr^="..."]Starts with…
  • [attr$="..."]Ends with…
  • [attr="..." i|s]Case sensativity

cssSusy

🙅🏻‍♀️ Please don’t use this 🙅🏻‍♀️

Define the Day

<main style="
--day-start: 9,
--day-end: 18
"
>

Define the Events

<section style="
--event-start: 13,
--event-end: 14
"
>
html {
--h: 330;
--s: 100%;
--l: 34%;
--color: hsl(var(--h), var(--s), var(--l));
}

Hue is Radial

* {
--complement: calc(var(--h) - 180);
background: hsl(var(--complement), var(--s), var(--l));
}

Lightness is “Clamped

* {
--threshold: 55;
--contrast: calc((var(--l) - var(--threshold)) * -100%);
color: hsl(var(--h), var(--s), var(--contrast));
}

Defined Properties

For Changing State/Type

Custom properties Resolve Before Inheriting

color: hotpink;
color: oklch(0.45 0.20 352);
color: not-real;

Parse Time

  • color: hotpink;
  • color: oklch(0.45 0.20 352);
  • color: not-real;

Declared Values

h1, h2, h3 {
color: aqua;
}

#page-title {
color: currentColor;
}

Cascaded Value

#page-title {
color: currentColor;
}

Specified Value

Cascaded or Defaulted

  • color: currentColor; - cascaded
  • display: block - cascaded (from UA)
  • font-family - inherited (from parent)
  • background-color: transparent - initial (from spec)

Global keywords

inherit, initial, unset, revert, revert-layer

Unset

Use defaulted value (inherit or initial)

Revert

Use previous origin value (e.g. UA defaults)

Revert-Layer

Use previous layer value (including origins as layers)

Global keywords Resolve on Custom Properties

html {
--color: green;
--color: initial;
background: initial;
background: var(--color, red); /* ??? */
}

Computed Value

Relative values and variables are replaced

  • color: currentColor » rgb(0, 0, 0)
  • padding: 2em » ( * 2) » 48px (for example)

In most cases… Computed Values Inherit

Global keywords are an exception

currentColor Inherits as Keyword

It should re-calculate when the color changes!

Used Value

Final resolutions, based on layout etc

  • width: 80% » 1223px (for example)
  • color-scheme: dark light » dark (for example)
  • flex: 1 » (nothing on non-flex items)

Actual Value

What is displayed on the screen

  • font-size: 14.2px » 14px

Custom properties… Inherit Computed Values

Custom properties… Invalid At Computed Value Time

html {
@media (prefers-color-scheme: dark) {
--os-mode: -1;
}

@media (prefers-color-scheme: light) {
--os-mode: 1;
}
}
[data-colors='light'] {
--html-mode: 1;
}

[data-colors='dark'] {
--html-mode: -1;
}
[data-colors] {
--mode: var(
--html-mode, var(
--user-mode, var(
--os-mode, 1
)
)
);
}
<div style="--index: {{ loop.index }};">
[style*='--index'] {
animation-delay: calc(var(--index) * 50ms);
}
<div style="--ease: var(--in-out-back);">
[style*='--ease'] {
--in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
--out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--in-out-back: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
<section
class="sprite-demo"
:style="{
'--src': show.sprite.src,
'--columns': show.sprite.columns,
'--rows': show.sprite.rows,
}"
>
...</section>
<div
v-for="action in show.actions"
:key="action.name"
:data-action="action.name"
:style="{
'--row': action.row,
}"

/>

Transition & Animate Target Properties

Not the variable itself…

button {
--color: green;
background: var(--color);
transition: background 0.5s;

&:hover {
--color: red;
}
}

Proposed CSS

@property --brand-color {
syntax: "<color>";
initialValue: "pink";
inherits: true;
}

Current JavaScript

CSS.registerProperty({
name: "--brand-color",
syntax: "<color>",
initialValue: "pink",
inherits: true,
});

Initial values Must be Absolute

When the syntax is not *

Variable Issues Content Requires Quoted Values

div::after {
--string: 'hello world';
content: var(--string);
}

@supports ( --css: vars )

Any valid definition will work…

Custom Properties Are Not Just Variables

(as we’ve known them)

  • Global Tokens
  • Reusable Mixins
  • Pre-Compiled Functions

Custom Properties Dynamic & Responsive Styling

  • Contextual tokens
  • Dynamic Visualizations
  • Component Settings
  • User Interactions