Tilbage til Custom by Design

3. semester / Tema 13C: Frontend Design

Kontekst og container style queries

Lad komponenter reagere på nedarvede værdier, typed attr() og betingelser for plads og indhold.

Se undervisningsslides

Relevante links

Arv er ikke det samme som en selector

p {
color: green;
}
#context {
color: red;
}

Et p inde i #context bliver grønt: en direkte værdi på barnet har forrang over en værdi, det ellers ville arve. Forælderens id-selector gør ikke den arvede værdi stærkere.

I øvelsens starter matcher både .inner p og .outer p det inderste afsnit. De har samme specificitet, så den senere regel vinder. Prøv at forudsige farverne, før du viser resultatet. Skift derefter til lokale custom properties:

p {
color: var(--paragraph, green);
}
.outer {
--paragraph: red;
}
.inner {
--paragraph: green;
}

Nu kan det indre område overskrive konteksten, som afsnittene arver. Øvelsen bygger videre i fire løsningstrin:

  1. Fra selectors til arv: p læser --paragraph, mens .outer og .inner sætter værdien lokalt. I starteren bliver begge afsnit røde; med custom properties bliver det yderste rødt og det inderste grønt. Det skyldes, at værdierne nu sættes på forskellige forældre og arves, frem for at to selectors konkurrerer om samme afsnit.
  2. Kontekst inde i kontekst: Der tilføjes endnu en .outer inde i .inner. CSS er uændret, men farverne bliver rød → grøn → rød. Den nye .outer sætter sin egen værdi, som dens afsnit arver.
  3. Fra værdi til flere regler: @container style(--paragraph: green) giver afsnittet fed skrift og lysegrøn baggrund. var() leverer farven direkte; style query’en bruger samme kontekst som betingelse for andre egenskaber. Den undersøger en forælders værdi, ikke afsnittets egen.
  4. Navngiv konteksten i HTML: data-context="a" og "b" erstatter klasserne. [data-context] læser attributten med --context: attr(data-context type(a | b)), og afsnittenes style queries vælger tekstfarve, vægt og baggrund. Konteksten beskriver nu en variant frem for en bestemt farve.

Typed attr() læses kun på elementer med data-context; værdien kan derefter arves af indholdet. type(a | b) giver keywordet a eller b, som matcher query’en, frem for en tekststreng. Dette sidste trin kræver støtte til typed attr() ud over style queries.

Forudsig → afprøv → forklar: Gæt resultatet før hvert trin. Skift til sidst den inderste data-context fra a til b, og fjern derefter attributten helt. Når attributten fjernes, arver området konteksten b udefra. Forklar forskellen på at sætte en lokal værdi og at arve en.

Fra størrelse til kontekst

En size query undersøger en egnet forælders størrelse. En style query undersøger her værdien af en custom property på en forælder og anvender regler på børnene.

.section { --surface: dark; }
.button-secondary {
color: #18202b;
background: transparent;
border: 1px solid currentColor;
@container style(--surface: dark) {
color: white;
}
}

Style queries på custom properties kræver ikke container-type: inline-size. Queryen undersøger ikke elementets egen værdi for at style samme element. Giv derfor omgivelserne konteksten, og lad indholdet reagere.

Knappens variant og omgivelser

I demoen har knappen en egen variant, mens området har et lyst eller mørkt tema. Det er to uafhængige valg. Typed attr() læser attributten på det element, hvor værdien defineres:

[data-theme] {
--theme: attr(data-theme type(light | dark), light);
}

Typen betyder, at værdien skal være keywordet light eller dark. Uden den type er attr() som udgangspunkt en streng, som ikke er det samme som keywordet i style(--theme: dark).

Prøv: Flyt samme knapvariant mellem områderne eller ændr områdets data-theme. Forklar, hvilke indstillinger der tilhører knappen, og hvilke den får fra omgivelserne.

Indlejrede temaer og density

Den næste demo har en mørk editor, et lyst preview og områder med kompakte eller rummelige knapper. Tema og density kan arves og ændres uafhængigt. Attributterne læses kun på elementer, der har dem; øvrige efterkommere arver værdierne.

Prøv: Skift kun data-density i det indre område. Kontrollér, at temaet stadig kommer fra den forventede forfader. Skift derefter kun temaet.

Samme kort med forskellig tæthed

Her bestemmer sektionens data-density, om det samme kort vises kompakt, normalt eller rummeligt. Typed attr() læser konteksten som --density, og kortets style queries vælger en lokal --density-scale: 0.72, 1 eller 1.3.

Skalaen bruges i calc() til blandt andet padding, gap, billedbredde, overskrift og knappens størrelse. Sektionen angiver dermed en hensigt, mens kortet selv bestemmer, hvilke dele der følger med. Uden en matchende density beholder kortet sin grundværdi på 1.

Prøv: Skift den første sektions data-density fra compact til spacious, og fjern derefter attributten. Hvilke dele ændrer sig sammen? Overvej også, om tekst og knapper stadig er store nok i den kompakte udgave — alle størrelser behøver ikke følge samme skala.

Hvorfor ikke bare en klasse?

En klasse eller attributselector er ofte nok til en enkelt variant. Style queries bliver nyttige, når en nedarvet værdi kan komme fra flere forskellige omgivelser, eller når CSS selv ændrer værdien. En selector som .dark .button matcher stadig gennem en indre lys sektion; lokale custom properties kan overskrive den arvede kontekst.

Brug var() direkte, når du blot skal bruge en værdi. Brug en style query, når værdien skal udløse flere regler. Range style queries kan desuden sammenligne numeriske intervaller; det er en særskilt udvidelse, som ikke er nødvendig for øvelserne her.

Style queries passer også godt til komponenter i fx Astro: Omgivelserne definerer en kontekst med custom properties, mens hver komponent i sin egen CSS bestemmer, hvordan den reagerer på konteksten. En sektion kan dermed angive et mørkt tema uden at skulle kende eller style de enkelte komponenters indre struktur. Almindelige custom properties kan allerede levere værdier på tværs af komponentgrænser; style queries lader komponenten omsætte konteksten til flere CSS-regler, fx ændringer i layout, kanter og afstande.

Kontekst fra indhold og plads

Følgende demo opbygger betingelsen i to trin: først mindst tre børn med :has(> :nth-child(3)), derefter kombineret med en smal size-container. Betingelsen sætter --warning: true; kortene reagerer med en style query.

Den røde farve er en synlig markering af, at betingelsen er opfyldt. Som videreudvikling kan samme mekanik vælge en kompakt præsentation med mindre padding. Afprøv både få/mange kort og smal/bred container.

Et layoutvalg som fælles besked

Produkt-demoen adskiller tre ansvar:

  1. .product-slot er den navngivne size-container.
  2. .product-card vælger --layout: stacked eller inline, afhængigt af pladsen, og er navngivet product.
  3. .purchase reagerer med @container product style(--layout: inline).

Det giver en fælles besked, som flere dele af produktkortet kan reagere på. I denne lille demo kunne en direkte size query også løse opgaven; gevinsten bliver større, når flere dele skal følge samme beslutning.

Øvelse: Tematiserede sektioner

Her samles kontekst og lokale komponentværdier. Steps-udgaven bygger løsningen op i følgende rækkefølge:

  1. Sektionerne får baggrundsfarver gennem custom properties.
  2. Overskrifter får farve efter sektionens rolle.
  3. Kortet får egne værdier for baggrund, overskrift og tekst.
  4. Sektionerne annoncerer --surface: default, dark eller muted.
  5. Kortets style queries vælger passende værdier ud fra overfladen.

Kortet skal fortsat have en læsbar grundopsætning. Test kontrast på alle overflader, også for hjælpetekst.

Samme mønster kan bruges i Astro. Undersøg, hvor sektionens kontekst sættes, og hvor komponenten reagerer: