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
- Typed attr()
- 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
- Scroll-snap
- Invoker commands
- Star-rating
- 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 */Kontekstdrevet UI
By Dannie Vinther
Kontekstdrevet UI
- 135