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 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

Check For A11y

When manipulating data tables

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