CSS 2023

Nogle demo kræver Chrome Canary

CSS 2023

  • Fra "CSS3" til "CSS4"
    • New responsive, CSS-arkitektur,
      HDR farver, typografi
  • CSS vs. JavaScript
    • Math
    • Component-arkitektur
    • Interaktion & performance
  • Fortjener CSS mere opmærksomhed?

Container queries

Scroll-driven animation

View Transitions

HDR colors

Typografi

Cascade layers

<dialog>

popover

anchoring

State/style queries

@layer theme {
  html {
    --color: oklch(80% 2 220);
  }

  * {
    text-box-trim: both;
    text-box-edge: cap alphabetic;
  }
}

@layer component {
  div {
    margin-trim: block;
    padding: 2rlh;

    :is(article):has(&) {
      --rainbow-gradient: in oklch longer hue, red 0 0;
      
      padding-inline: max(1rem, (100% - 800px) / 2);
      background: linear-gradient(45deg var(--rainbow-gradient));
      container: div / inline-size;
    }

    :is(article) & {
      border: 2px solid color(display-p3 1 none 1);
      background: oklch(80% .37 220 / 25%);

      @container div (inline-size > 35rem) {
        font-size: calc(1rem * pow(1.25, 1));
      }
    }

    :is(aside) {
      container: aside / sticky;
      position: sticky;
      inset-block-start: 0;
      
      @container aside state(stuck) {
        & h2 {
          background: red;
        }
      }
    }

    :is(h2, h3) {
      inline-size: 17ch;
      text-wrap: balance;
    }

    > h2 {
      background: hsl(from var(--color) h s calc(l + 10%));
    }

    :not(article) & :nth-child(2 of .paragraph) {
      &:first-letter {
        initial-letter: 3;
        margin-inline-end: 1ch;
      }
    }

    & section {
      max-inline-size: 400px;

      > p {
        text-wrap: pretty;
      }

    }
  }
}
@layer theme {
  html {
    --color: oklch(80% 2 220);
  }

  * {
    text-box-trim: both;
    text-box-edge: cap alphabetic;
  }
}

@layer component {
  div {
    margin-trim: block;
    padding: 2rlh;

    :is(article):has(&) {
      --rainbow-gradient: in oklch longer hue, red 0 0;
      
      padding-inline: max(1rem, (100% - 800px) / 2);
      background: linear-gradient(45deg var(--rainbow-gradient));
      container: div / inline-size;
    }

    :is(article) & {
      border: 2px solid color(display-p3 1 none 1);
      background: oklch(80% .37 220 / 25%);

      @container div (inline-size > 35rem) {
        font-size: calc(1rem * pow(1.25, 1));
      }
    }

    :is(aside) {
      container: aside / sticky;
      position: sticky;
      inset-block-start: 0;
      
      @container aside state(stuck) {
        & h2 {
          background: red;
        }
      }
    }

    :is(h2, h3) {
      inline-size: 17ch;
      text-wrap: balance;
    }

    > h2 {
      background: hsl(from var(--color) h s calc(l + 10%));
    }

    :not(article) & :nth-child(2 of .paragraph) {
      &:first-letter {
        initial-letter: 3;
        margin-inline-end: 1ch;
      }
    }

    & section {
      max-inline-size: 400px;

      > p {
        text-wrap: pretty;
      }

    }
  }
}

Det ligner CSS, men det føles som et nyt sprog

— "CSS4" måske?

"CSS4"

2012 - 2017

Intrinsic web

2017 -

layout?

"CSS4"

Intrinsic web

2017 -

layout?

"CSS4"

2023 - 

Intrinsic web

Robust, pålidelig og fleksibel

New responsive

  • Flex & grid
  • Container queries
  • (Viewport units)
  • Style queries
  • :has()
  • (nth-child of S)

Flexbox & grid

.grid {
  align-items: last baseline;
  grid-template-columns: subgrid;
  grid-template-rows: masonry;
  reading-order-items: grid rows;
}

Flexbox & grid

.grid {
  align-items: last baseline;
  grid-template-columns: subgrid;
  grid-template-rows: masonry;
  reading-order-items: grid rows;
}

Nye funktionaliteter

Baseline alignment

.subgrid {
  display: grid;
  grid-template-rows:
    [system-status] 3.5rem
    [primary-nav] 3rem
    [primary-header] 4rem
    [main] auto
    [footer] 4rem
    [system-gestures] 2rem;
  grid-template-columns:
    [fullbleed-start] 1rem
    [main-start] auto
    [main-end] 1rem
    [fullbleed-end];
}

Subgrid

nav {
  grid-area: primary-nav / fullbleed;
  
  display: grid;
  grid-template-columns: subgrid;
}

nav > .content {
  grid-area: main;
}

Subgrid

Subgrid

Loading...

.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

Subgrid

Loading...

Subgrid

Loading...

.masonry {
  display: grid;
  grid-template-columns:
    repeat(auto-fill, minmax(14rem, 1fr));
  grid-template-rows: masonry;
}

Masonry

.masonry {
  display: grid;
  grid-template-columns: 1fr 2fr 3fr;
  grid-template-rows: masonry;
}

Masonry

Source order

Fleksibelt nok?

truly flexible

Container queries

Container queries

vs media queries

Container queries

Container queries

@container (inline-size > 40em) {
  .card {
    flex-direction: column;
  }
}

width

range

@media (20em < width <= 40em) {
  ...
}

Container queries

.card-wrapper {
  container-type: inline-size;
}

@container (inline-size > 40em) {
  .card {
    flex-direction: column;
  }
}

Container queries

Container queries

Container queries

.card-wrapper {
  container-type: inline-size;
  container-name: main;
}

@container main (inline-size > 40em) {
  .card {
    flex-direction: column;
  }
}

Container queries

.card-wrapper {
  container: main / inline-size;
}

@container main (inline-size > 40em) {
  .card {
    flex-direction: column;
  }
}

flere navne

Container query units

.card-wrapper {
  container: main / inline-size;
}

.card {
  font-size: clamp(1.45rem, 6cqi, 1.75rem);
}

cqw, cqh, cqi, cqb, cqmin, cqmax

6 % af inline (bredden)

Container query units

Container query units

Sidenote: Viewport units

dvh > vh

Container queries

Containment spec

enables querying a parent's style values

enables querying a parent's state

enables querying a parent's size

@container (inline-size > 40em)
@container style(font-style: italic)
@container state(stuck)

snapped, overflowing

Containment spec

enables querying a parent's state

Style queries

@container style(--theme: fun) {
}

Style queries

@container style(--theme: fun) {
  button {
    background: var(--bg);
    border: thick solid mediumvioletred;
    border-radius: 1em 0;
    box-shadow: var(--shadow-xl);
    font-weight: bold;
    padding: 0.5em 1em;
  }
}
.fun-section {
  --theme: fun;
}

Style queries

@container style(--theme: fun) {
  button {
    background: var(--bg);
    border: thick solid mediumvioletred;
    border-radius: 1em 0;
    box-shadow: var(--shadow-xl);
    font-weight: bold;
    padding: 0.5em 1em;
  }
}
.fun-section {
  --theme: fun;
}

Style queries

<article style="--quote-type: highlight">
  <div class="blockquote">
    <blockquote>
      Underline your fucking links you sociopaths.
    </blockquote>
    <p><em>Heydon Pickering</em></p>
  </div>
</article>

Style queries

Style queries

Style queries

:root {
  --theme: light;
}

site-header {
  --nav-style: main;
}

.intro {
  --paragraph-style: initial-letter;
}

.selected-posts {
  --card-style: large;
}

applying global, per page, and per section styles in CSS

Fewer presentational class names

Lea Verou

Manuel Matuzović

Style queries

Manuel Matuzović

Style queries

.navigation__menu:has(:nth-child(6)) {
  --show-menu: true;
}

@container menu (50ch <= inline-size <= 80ch) {
  @container not style(--show-menu: true) {
    .navigation__menu button {
      display: none;
    }
    
    .navigation__menu ul {
      display: flex;
    }
  }
}

:has()

meget snart!

:has()

:has()

artcle:has(> img) {}

p:not(article > *) { ... }

article > :is(.class, #id) { ... }

:where(#article) a { ... }

:is(), :where(), :not()

:has()

article:has(h2) { ... }

article:not(:has(h2)) img { ... }

article:has(> :last-child:nth-child(3)) { ... }

:has()

:has()

:has()

:has()

:has()

:has()

:has()

:has()

:root:has(dialog[open]) {
  overflow: hidden;
}

:has()

:nth of S

CSS-arkitektur

  • Nesting
  • Cascade layers
  • @scope

Nesting

Nesting

meget snart!

Nesting

section {
  border: 2px solid red;
  
  
    color: red;
  }
}

h2 {

Nesting

section {
  border: 2px solid red;
  
  
    color: red;
  }
}

&
h2 {

Nesting

section {
  border: 2px solid red;
  
  
    color: red;
  }
}

>
h2 {

Nesting

section {
  border: 2px solid red;
  
  
    color: red;
  }
}

:is(  )
h2 {

Nesting

  1. Red
  2. Blue
  3. The syntax is invalid

Hoisted

Nesting

.feature-list {
    display: grid;
    grid: "stack";

    :is(img, ul) {
      grid-area: stack;
    }

    > ul {
      z-index: 1;
      place-self: center start;
      
      > li {
        animation: opacity linear both;
        animation-timeline: view();
        
        &:hover {
          opacity: .8;
        }
      }
    }
  }

Co-location

Nesting

Nesting

Cascade layers

"reset" / "normalize"

Quiz

p:not(#id) {
  color: green;
}

p.class {
  color: red;
}
<p class="class">text</p>

<p id="id">last text</p>

The cascade

Author styles

0,0,0

0,0,1

🤷‍♂️

Cascade layers

@layer reset {
  /* ... other reset rules */
  
  input[type="text"] {
    border: 1px solid gray;
  }
}

@layer components {
  /* ... other component rules */
  
  .my-input {
    border: 4px solid blue;
  }
}

order matters

Cascade layers

@layer reset, components, utilities;

@layer reset {
  /* ... other reset rules */
  
  input[type="text"] {
    border: 1px solid gray;
  }
}

@layer components {
  /* ... other component rules */
  
  .my-input {
    border: 4px solid blue;
  }
}

configure up front

1

2

3

Cascade layers

@layer reset, components, utilities;

@layer components {
  /* ... other component rules */
  
  .my-input {
    border: 4px solid blue;
  }
}

@layer reset {
  /* ... other reset rules */
  
  input[type="text"] {
    border: 1px solid gray;
  }
}

configure up front

1

2

3

Cascade layers

@layer reset, components, states;

@layer components {
  /* Buttons, inputs etc. */
}

@layer states {
  /* Make sure stats override, no matter specificity count */
  :disabled {
    background-color: #ddd;
    color: #999;
  }
  
  :focus-visible {
    outline: 2px solid var(--focus-color, currentColor);
    outline-offset: 2px;
  }
}

Cascade layers

@layer reset, theme, global, layout, components, utilities, states;

Quiz

p {
  color: green;
}

#context {
  color: red;
}
<div id="context">
  <p>Some text</p>
</div>

@scope

@property --colorPrimary {
  syntax: '<color>';
  initial-value: magenta;
  inherits: false;
}

Loading...

@scope

@property --colorPrimary {
  syntax: '<color>';
  initial-value: magenta;
  inherits: false;
}

Loading...

@scope

@scope (.media-block) to (.content) {
  img { border-radius: 50%; }
}
<div class="media-block">
  <img src="..."> <!-- .media-block scope -->
  <div class="content"> <!-- scope end -->
    <img src="..."> <!-- NOT in scope -->
  </div>
</div>

Donut scope

@scope

@property --colorPrimary {
  syntax: '<color>';
  initial-value: magenta;
  inherits: false;
}

Loading...

HDR Colors

  • Wide color gamut
  • Color functions
  • Gradients
.valid-css-color-function-colors {
  --srgb: color(srgb 1 1 1);
  --srgb-linear: color(srgb-linear 100% 100% 100% / 50%);
  --display-p3: color(display-p3 1 1 1);
  --rec2020: color(rec2020 0 0 0);
  --a98-rgb: color(a98-rgb 1 1 1 / 25%);
  --prophoto: color(prophoto-rgb 0% 0% 0%);
  --xyz: color(xyz 1 1 1);
}

Color spaces

.most-hyped {
  --display-p3: color(display-p3 1 1 1);
  --rec2020: color(rec2020 0 0 0);
}

Color spaces

P3 color gamut

oklab, oklch

HDR colors

.element {
  background: oklch(80% 2 220);
}

lightness

chroma

hue

oklch()

oklch()

oklch()

oklch()

In OKLCH all backgrounds with L≥87% have good contrast with black text.

oklch()

oklch()

Gradients

Gradients

color-mix()

color-mix()

color-mix()

color-mix()

Linear mapping

color-mix()

Linear mapping

Relative color syntax

Typography

  • text-box-trim
  • margin-trim
  • text-balance
  • lh, rlh & cap
  • initial-letter
  • color fonts

How do you center a div?

Centering stuff

😩

.element {
  display: flex;
  gap: 1.25ch;
  align-items: center;
}

????

How do you center this?

Centering stuff

😩

Overskrift

Morbi tortor mi, semper id magna quis, auctor auctor purus. Nam pellentesque vulputate lectus, et euismod est tincidunt sed.

Overskrift

Overskrift

Overskrift

line box

giraf

Figma: Vertical trim

text-box-trim

no more alignment issues

h1 { 
 text-box-trim: both;
 text-edge: cap alphabetic;
}

🥹

Overskrift

Morbi tortor mi, semper id magna quis, auctor auctor purus. Nam pellentesque vulputate lectus, et euismod est tincidunt sed.

text-box-trim

h1 {
  text-box-trim: both;
  text-edge: cap alphabetic;
  /* future? */
  /* text-box-trim: cap alphabetic; */
}

margin-trim

margin-trim

article {
  margin-trim: block;
}

Ikke mere * > * eller display: grid;

text-wrap

h1 {
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

lh & rlh

Vertical rythm

article {
  padding: 2rlh;
  gap: 1rlh;
}

lh & rlh

Multi-line colors

Initial letter

p:first-letter {
  initial-letter: 3;
}

Initial letter

COLRv1 & font-palette

@font-face {
  font-family: 'Rocher';
  src: url('/fonts/RocherColorGX.woff2');
}

h1 {
  font-family: "Rocher";
}

COLRv1 & font-palette

@font-face {
  font-family: 'Rocher';
  src: url('/fonts/RocherColorGX.woff2');
}

h1 {
  font-family: "Rocher";
}

COLRv1 & font-palette

@font-palette-values --pink {
  font-family: 'Rocher';
  base-palette: 1;
}

h1 {
  font-palette: --pink;
}

COLRv1 & font-palette

@font-palette-values --pink {
  font-family: 'Rocher';
  base-palette: 1;
}

h1 {
  font-palette: --pink;
}

CSS vs JavaScript

Intrinsic web

Hvorfor griber man typisk fat i JS?

Trigonometri i CSS

Trigonometri i CSS

Trigonometri i CSS

Math Expressions

Comparison Function
min(), max(), clamp()

Stepped Value Functions

round(), mod(), rem()

Trigonometric Functions
sin(), cos(), tan(), asin(), acos(), atan(), atan2()

Exponential Functions
pow(), sqrt(), hypot(), log(), exp()

Sign-Related Functions
abs(), sign()

Numeric Constants
e, pi

Degenerate Numeric Constants
infinity, -infinity, NaN

Er CSS snart et programmeringssprog? 😉

Inert, Esc dismiss,
top-layer,
@starting-style

<dialog>

<dialog>

Loading...

<dialog>

Loading...

@starting-style

Loading...

Tøjring af popovers

Popover & Anchoring

Popover & Anchoring

Loading...

Popover & Anchoring

Loading...

Popover & Anchoring

Loading...

Popover & Anchoring

<selectmenu>
  <option value="r" checked>Red</option>
  <option value="g">Green</option>
  <option value="b">Blue</option>
</selectmenu>

Select Menu

Stylable dropdowns

selectmenu {
  &:open { ... }
  
  &::part(button) { ... }
  
  &::part(marker) { ... }
  
  > option::before { ... }
  
  > option[value="r"]::before { background: red; }
}

Select Menu

Stylable dropdowns

built-in

selectmenu {
  &:open { ... }
  
  [slot="button"] { ... }
  
  [behavior="selected-value"] { ... }
  
  [slot="listbox"] { ... }
  
  [popover] option { ... }
}

Select Menu

slots & behavior

custom-built

<selectmenu>
  <button slot="button" behavior="button">
    <span behavior="selected-value"></span>
  </button>
  <div slot="listbox">
    <div popover behavior="listbox">
      <option value="one">one</option>
      <option value="two">two</option>
    </div>
  </div>
</selectmenu>
<selectmenu>
  <button slot="button" behavior="button">
    <span behavior="selected-value"></span>
  </button>
  <div slot="listbox">
    <div popover behavior="listbox">
      <option value="one">one</option>
      <option value="two">two</option>
    </div>
  </div>
</selectmenu>

Select Menu

slots & behavior

selectmenu {
  &:open { ... }
  
  [slot="button"] { ... }
  
  [behavior="selected-value"] { ... }
  
  [slot="listbox"] { ... }
  
  [popover] option { ... }
}

Select Menu

Loading...

const Popover = ({ children, id, type = "auto" }) => {
  return (
    <div popover={type} id={id}>
      <button popovertarget={id} popovertargetaction="hide">
        <span aria-hidden="true">&times;</span>
        <span className="sr-only">Close Popover</span>
      </button>
      {children}
    </div>
  );
};

Popover & Anchoring

.jsx example

const Popover = ({ children, id, type = "auto" }) => {
  return (
    <div popover={type} id={id}>
      <button popovertarget={id} popovertargetaction="hide">
        <span aria-hidden="true">&times;</span>
        <span className="sr-only">Close Popover</span>
      </button>
      {children}
    </div>
  );
};

Popover & Anchoring

const App = () => {
  const popoverId = crypto.randomUUID();
  return (
    <main>
      <h1>Popover API w/ React</h1>
      <button popovertarget={popoverId}>Toggle Popup!</button>
      <Popover type="auto" id={popoverId}>
        I'm a Popover!
        <span className="wave">👋</span>
      </Popover>
    </main>
    );
};

.jsx example

Popover, anchoring, @starting-style

Selectmenu

Animation

  • Individual transforms
  • @property
  • animation-composition
  • linear()
  • Scroll Driven Animations
  • View Transitions
.box {
  rotate: 45deg;
  translate: -50% -50%;
  scale: 1.1;
}

.box:hover {
  rotate: 0deg;
}

Individual Transforms

@property --colorPrimary {
  syntax: '<color>';
}

@property

@property --colorPrimary {
  syntax: '<color>';
  initial-value: magenta;
}

@property

@property --colorPrimary {
  syntax: '<color>';
  initial-value: magenta;
  inherits: false;
}

@property

@property

@property --colorPrimary {
  syntax: '<color>';
  initial-value: magenta;
  inherits: false;
}

— typed Custom Properties

Loading...

@property

@property --colorPrimary {
  syntax: '<color>';
  initial-value: magenta;
  inherits: false;
}

— typed Custom Properties

Loading...

@property

@property --colorPrimary {
  syntax: '<color>';
  initial-value: magenta;
  inherits: false;
}

— typed Custom Properties

Loading...

Composition

Composition

@property performance

Composition

Linear()

Easing

Linear()

Easing

Linear()

Easing

Scroll driven animation

Optimized composited scroll animations

Scroll driven animation

Not blocking main thread

Scroll driven animation

Loading...

Scroll driven animation

Loading...

Scroll driven animation

Loading...

Scroll driven animation

Loading...

.element {
  animation: rotate linear;
  animation-timeline: scroll(block root);
}

Scroll driven animation

Brug scrollbar som tidslinje

.element {
  animation: rotate linear;
  animation-timeline: view(inline);
  animation-range: entry;
}

Scroll driven animation

Hold øje med et element ift. viewporten

Scroll driven animation

Optimized flip-animations

View transitions

View Transitions

Loading...

View Transitions

Loading...

View Transitions

Loading...

View Transition

.box-1 {
  view-transition-name: box-1;
}

.box-2 {
  view-transition-name: box-2;
}

View Transition

.box-1 {
  view-transition-name: box-1;
}

.box-2 {
  view-transition-name: box-2;
}
btn.addEventListener("click", () => {
  document.startViewTransition(_ => grid.classList.toggle("col-count"));
});

View Transition

.box-1 {
  view-transition-name: box-1;
}

.box-2 {
  view-transition-name: box-2;
}
btn.addEventListener("click", () => {
  if (document.startViewTransition) {
    document.startViewTransition(_ => grid.classList.toggle("col-count"));
  } else {
    grid.classList.toggle("col-count");
  }
});

View Transition

.box-1 {
  view-transition-name: box-1;
}

::view-transition-new(box-1) {
  animation: scale-in .5s both;
}

::view-transition-old(box-1) {
  animation: scale-in .5s both reverse;
}

View Transition

View Transition (MPA)

View Transition

Hvad mangler?

CSS2023

By Dannie Vinther

CSS2023

  • 111