Kontekstdrevet UI

Et moderne overblik (og recap)

Web Dev, efterår 2026

konference, juni 2026

Speakers

Er frontend-faglighed stadig relevant i en tid med AI?

AI kræver faglig dømmekraft

AI kan hjælpe os med at producere hurtigere, men den kan ikke automatisk vide, hvad der er en god weboplevelse. Derfor kræver AI stadig mennesker, der kan orkestrere løsninger ud fra platformen, brugerne og konteksten.

Faglighed før automatisering

Hvis vi mister forståelsen for webplatformen, mister vi også evnen til at vurdere kvaliteten af det, som AI producerer.

Platformen er blevet voksen

Mange af konferencens emner handlede om, at browseren nu kan løse problemer, som tidligere krævede JavaScript, frameworks eller custom komponenter.

Popover & dialogs

Anchor Positioning

View Transitions

Custom Select

Container Queries

Scroll State Queries

if()

attr()

Grid-lanes

scroll-driven/triggered animations

contrast-color()

@function

Nye mentale modeller

Fx skal komponenter vide,

— Adam Argyle

hvor de er

hvad de indeholder

hvem de er sammen med

hvad brugeren foretrækker

Til sammenligning genererer AI ofte komponenter isoleret

-

-

-

-

-

UI-færdigheder

Hvad skal man vide i 2026?

-

-

-

-

Typed attr()

Container queries

Flex / Grid

Invoker Commands

Scroll animations

Anchor Positioning

View Transitions

User Preferences mm.

UI-færdigheder

Hvad skal man vide i 2026?

Hvor er vi?

Ift. HTML, CSS, JavaScript

Flexbox

Grid

Flex vs. Grid

Klassisk navigationsmenu

Flex vs. Grid

Enkelte rækker med både
Flexbox og Grid.

Flex vs. Grid

En række med kort

Flex vs. Grid

Responsivt layout, hvor rækkefølgen på billede og tekst skifter plads.

.grid {  
  display: grid;
  grid-template-columns:
    repeat(auto-fill, minmax(275px, 1fr));
}

pattern

min()

min()

pattern

min()

275px

275px

275px

pattern

min()

275px

275px

275px

Overflow

pattern

min()

100%

100%

100%

.grid {  
  display: grid;
  grid-template-columns:
    repeat(auto-fill, minmax(min(275px, 100%), 1fr));
}

Lav varianter med container style queries.

Notification Variants

Øvelse

  1. Typed attr()
  2. Container Style Queries

Opskrift:

Fra streng til datatype

[data-color]::after {
  content: attr(data-color);
}

Typed attr()

<h2 data-color="red"></h2>

Indsætter strengen "red"

Fra streng til datatype

[data-color] {
  color: attr(data-color type(<color>));
}

Typed attr()

Sætter tekstfarven

Fra streng til datatype

[data-color] {
  color: attr(data-color type(<color>), #fff);
}

Typed attr()

fallback

@scope

Fra streng til datatype

[data-color] {
  color: attr(data-color type(<color>), #fff);
}

Typed attr()

<h2 data-color="red">
  Make me red
</h2>

@scope

Fra streng til datatype

[data-color] {
  color: attr(data-color type(<color>), #fff);
}

Typed attr()

<h2 data-color="oklch(0.6 0.3 29)">
  Make me red
</h2>

@scope

Fra streng til datatype

Typed attr()

klik her

<div data-length="5rem"></div>
div {
  width: attr(data-length type(<length>));
  &::after {
    content: attr(data-length);
  }
}

Units vs. Types

Typed attr()

@scope

Units vs. Types

Typed attr()

<angle>

<color>

<custom-ident>

<image>

<integer>

<length>

<length-percentage>

<number>

<percentage>

<time>

Types of type()

Typed attr()

article {
  display: grid;
  grid-template-columns:
    repeat(8, 1fr);
  height: 300px;
}

div:nth-child(1) {
  height: 100%;
}
div:nth-child(2) {
  height: 70%;
}
...
<article>
  <div></div>
  <div></div>
  ...
</article>

Ikke så fleksibelt

10%
20%
30%
40%
50%
60%
70%
100%
article {
  display: grid;
  grid-template-columns:
    repeat(8, 1fr);
}

div:nth-child(1) {
  height: 100%;
}
div:nth-child(2) {
  height: 70%;
}
...
<article>
  <div style="height: 100%"></div>
  <div style="height: 70%"></div>
  ...
</article>

Data kommer typisk fra JS

10%
20%
30%
40%
50%
60%
70%
100%
bar.style.height = `${percent}%`
div {
  height: var(--height, 0%);
}
<article>
  <div style="--height: 100%"></div>
  <div style="--height: 70%"></div>
  ...
</article>

Det kunne ligeså godt se sådan ud

10%
20%
30%
40%
50%
60%
70%
100%
bar.style.setProperty("--height", `${percent}%`)
<article>
  <div data-percent="100"></div>
  <div data-percent="70"></div>
  ...
</article>

Eller sådan her

(en moderne løsning)

div {
  height: attr(data-percent %), 0);
}
10%
20%
30%
40%
50%
60%
70%
100%
bar.dataset.percent = String(percent)

Lad os prøve

.bar {
  height: attr(data-percent %); /* 70% */
}
<article class="bars">
  <div class="bar" data-percent="100"></div>
  <div class="bar" data-percent="70"></div>
  <!-- ... -->

Dynamisk navngivning

.card[id] {
  /* card-1, card-2, card-3, … */
  view-transition-name: attr(id type(<custom-ident>), none);
  view-transition-class: card;
}

Typed attr()

<div class="cards">
  <div class="card" id="card-1"></div>
  <div class="card" id="card-2"></div>
  <div class="card" id="card-3"></div>
  <div class="card" id="card-4"></div>
</div>

@scope

Ikke kun datatyper

div {
  --variant: attr(data-variant type(success | danger));
  
  @container style(--variant: success) {
    h2 { ... }
  }
  
  @container style(--variant: danger) {
    h2 { ... }
  }
}

Typed attr()

<main>
  <div data-variant="success">...</div>
  <div data-variant="danger">...</div>
</main>

attr()

div {
  width: attr(data-length type(<length>));
  &::after {
    content: attr(data-length);
  }
}

attr(data-number type(<number>), 0)

Container queries

Typed attr()

field-sizing

stretch

text-box / text-fit

Spørg containeren

Typed arithmetic

contrast-color()

sibling-index()/-count()

@media

@container

Container queries

Spørg containeren

@container

Container queries

Spørg containeren

  • size
  • name
  • style
  • scroll-state

Size queries

Style queries

Style queries

Style queries

Opret en custom property, der læser notification's data-variant med og sætter den som en keyword union; type(info | success).

 

Sæt derefter farve og ikon for hver kendt variant via
@container style()

Style Queries

Øvelse

Style queries (ranges)

Scroll-states

Byg denne testimonials-komponent med moderne (vanilla) HTML, CSS og JS

Testimonial

Øvelse

  1. Scroll-snap
  2. Invoker commands
  3. Star-rating
  4. Scroll-animaiton

Opskrift:

Scroll-snap

.scroll-container {
  scroll-snap-type: inline mandatory; /* block, proximity */
  overflow-inline: auto; /* scroll */
}

.item {
  scroll-snap-align: center; /* start, end */
}

Scroll-snap

Scroll-snap

Demo

Byg den horisontale scroller

Scroll-snap

Øvelse

Invoker Commands

Deklarativ interaktion

<button
  command="toggle-popover"
  commandfor="popover-1">
  Open Popover
</button>

<div popover id="popover-1">
  <!-- indhold -->
</div>

Invoker Commands

Invoker Commands

Custom commands

Invoker Commands

Custom commands

Invoker Commands

Custom commands

Invoker Commands

Custom commands

const scroller = document.querySelector("#scroller");

scroller.addEventListener("command", (e) => {
  if (e.command === "--scroll") {
    scroller.scrollBy({
      left: scroller.offsetWidth * e.source.value
    })
  }
});
<button
  value="-.9"
  command="--scroll"
  commandfor="scroller">
  Prev
</button>

Invoker Commands

Custom commands

const scroller = document.querySelector("#scroller");

scroller.addEventListener("command", (e) => {
  if (e.command === "--scroll") {
    scroller.scrollBy({
      left: scroller.offsetWidth * e.source.value
    })
  }
});
<button
  value="-.9"
  command="--scroll"
  commandfor="scroller">
  Prev
</button>

Disabled buttons

disabled

function disableButtons() {
  const maxScroll = scroller.scrollWidth - scroller.offsetWidth;

  prev.disabled = scroller.scrollLeft <= 0;
  next.disabled = scroller.scrollLeft >= maxScroll - 1;
}

scroller.addEventListener("scroll", disableButtons);

disableButtons()

Hele bredden

Den synlige bredde

boolean

boolean

Sæt invoker commands på knapperne og reager på dem i JavaScript. Sørg for, at knapperne bliver inaktive, når man ikke kan scrolle længere.

Invoker Commands

Øvelse

Tegn med gradienter

Star Rating

.gradient-progress {
  background: linear-gradient(
    90deg,
    [FARVE 1] [PROCENT],
    [FARVE 2] 0
  );
}

Tegn med gradienter

Tegn med gradienter

linear-gradient progress

Tegn med gradienter

mask

Tegn star-ratingen med linear-gradient() og mask ud fra tallet i [data-rating].

Star Rating

Øvelse

Scroll-indikator

Scroll-driven animation

Loading...

Scroll-driven animation

.element {
  animation: rotate 2s linear;
}

Tid

0s

2s

1s

Scroll

0%

100%

50%

Time-based vs. Scroll-driven

.element {
  animation: progress linear both;
  animation-timeline: scroll();
}

Scroll-driven animation

Brug dokumentet som tidslinje

Scroll-driven animation

Scroll-driven animation

timeline-scope

Scroll-driven animation

Scroll-driven animation

Byg den scroll-drevne progressindikator med timeline-scope.

Scroll-indikator

Øvelse

Byg siden efter bedste evne.

Responsive Design

Selvstændig opgave

Tips & Tricks

Hero

Conditional border-radius

Fylder containeren viewporten?

.hero-wrapper {
  container: hero-wrapper / inline-size;
}

article {
  border-radius: 2rem;
  @container (width = 100vw) {
    border-radius: 0;
  }
}

Conditional border-radius

progress()

Flydende størrelser med progress()

progress()

... returnerer et tal mellem 0 og 1, der repræsenterer en værdis progression fra en startværdi til en slutværdi.

progress(5, 0, 10) /* 0.5 */