Et moderne overblik (og recap)
Web Dev, efterår 2026
konference, juni 2026
Speakers
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.
Hvis vi mister forståelsen for webplatformen, mister vi også evnen til at vurdere kvaliteten af det, som AI producerer.
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
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
-
-
-
-
-
Hvad skal man vide i 2026?
-
-
-
-
Typed attr()
Container queries
Flex / Grid
Invoker Commands
Scroll animations
Anchor Positioning
View Transitions
User Preferences mm.
Hvad skal man vide i 2026?
Ift. HTML, CSS, JavaScript
Klassisk navigationsmenu
Enkelte rækker med både
Flexbox og Grid.
En række med kort
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
pattern
275px
275px
275px
pattern
275px
275px
275px
Overflow
pattern
100%
100%
100%
.grid {
display: grid;
grid-template-columns:
repeat(auto-fill, minmax(min(275px, 100%), 1fr));
}Lav varianter med container style queries.
Øvelse
Opskrift:
Fra streng til datatype
[data-color]::after {
content: attr(data-color);
}
<h2 data-color="red"></h2>
Indsætter strengen "red"
Fra streng til datatype
[data-color] {
color: attr(data-color type(<color>));
}
Sætter tekstfarven
Fra streng til datatype
[data-color] {
color: attr(data-color type(<color>), #fff);
}
fallback
Fra streng til datatype
[data-color] {
color: attr(data-color type(<color>), #fff);
}
<h2 data-color="red">
Make me red
</h2>
Fra streng til datatype
[data-color] {
color: attr(data-color type(<color>), #fff);
}
<h2 data-color="oklch(0.6 0.3 29)">
Make me red
</h2>
Fra streng til datatype
klik her
<div data-length="5rem"></div>div {
width: attr(data-length type(<length>));
&::after {
content: attr(data-length);
}
}Units vs. Types
Units vs. Types
<angle>
<color>
<custom-ident>
<image>
<integer>
<length>
<length-percentage>
<number>
<percentage>
<time>
Types of type()
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;
}
<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>
Ikke kun datatyper
div {
--variant: attr(data-variant type(success | danger));
@container style(--variant: success) {
h2 { ... }
}
@container style(--variant: danger) {
h2 { ... }
}
}
<main>
<div data-variant="success">...</div>
<div data-variant="danger">...</div>
</main>
div {
width: attr(data-length type(<length>));
&::after {
content: attr(data-length);
}
}attr(data-number type(<number>), 0)
Typed attr()
field-sizing
stretch
text-box / text-fit
Spørg containeren
Typed arithmetic
contrast-color()
sibling-index()/-count()
@media
@container
Spørg containeren
@container
Spørg containeren
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()
Øvelse
Byg denne testimonials-komponent med moderne (vanilla) HTML, CSS og JS
Øvelse
Opskrift:
.scroll-container {
scroll-snap-type: inline mandatory; /* block, proximity */
overflow-inline: auto; /* scroll */
}
.item {
scroll-snap-align: center; /* start, end */
}Demo
Byg den horisontale scroller
Øvelse
Deklarativ interaktion
<button
command="toggle-popover"
commandfor="popover-1">
Open Popover
</button>
<div popover id="popover-1">
<!-- indhold -->
</div>
Custom commands
Custom commands
Custom 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>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
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.
Øvelse
Star Rating
.gradient-progress {
background: linear-gradient(
90deg,
[FARVE 1] [PROCENT],
[FARVE 2] 0
);
}
linear-gradient progress
mask
Tegn star-ratingen med linear-gradient() og mask ud fra tallet i [data-rating].
Øvelse
Scroll-driven animation
Loading...
.element {
animation: rotate 2s linear;
}0s
2s
1s
0%
100%
50%
.element {
animation: progress linear both;
animation-timeline: scroll();
}Brug dokumentet som tidslinje
timeline-scope
Byg den scroll-drevne progressindikator med timeline-scope.
Øvelse
Byg siden efter bedste evne.
Selvstændig opgave
Hero
Fylder containeren viewporten?
.hero-wrapper {
container: hero-wrapper / inline-size;
}
article {
border-radius: 2rem;
@container (width = 100vw) {
border-radius: 0;
}
}Flydende størrelser med 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 */