CRAFTING UI

Frontend Design

Custom by Design

Custom properties, style queries & custom commands

Agenda

Custom Properties

Style Queries

Custom Commands

Agenda

  1. Forstå                           bedre

  2. Forstå, hvordan man opbygger robuste layouts med

  3. Kan            -funktioner hjælpe med layout?

  4. Øvelser for at opbygge praktisk erfaring

Custom Properties

Style Queries

Custom Commands

Custom Properties

Custom Properties

  • Tokens: Genbrug farver, afstande og skriftstørrelser, så designet bliver ensartet.
  • House props: Giv variabler navne efter formål, fx --brand og --card-padding.
  • Adaptive props: Skift variabelværdier til dark mode eller forskellige skærmstørrelser via media queries.
  • Lokale komponentvariabler (pseudo-private props): Saml en knaps eller et korts værdier lokalt, og tilpas dem ved fx :hover. --_ er kun en navnekonvention.
  • Partial props: Brug variabler som dele af en værdi, fx en fælles farvetone i flere hsl()-farver eller en størrelse i calc().
.card {
  padding: 1rem;
  
  @container (width > 30ch) {
    padding: 2rem;
  }
}

Smart?

.card {
  padding: 1rem;
  
  @container (width > 30ch) {
    padding: 2rem;
  }
  
  h2 {
    font-size: 1.5rem;
    
    @container (width > 30ch) {
      font-size: 2.5rem;
    }
  }
  
  .card-actions {
    @container (width > 30ch) {
      flex-direction: row;
    }
  }
}

Er det en god ide?

.card {
  padding: 1rem;
  
  h2 {
    font-size: 1.5rem;
  }
  
  @container (width > 30ch) {
    .card {
      padding: 2rem;
    }
    
    h2 {
      font-size: 2.5rem;
    }
    
    .card-actions {
      flex-direction: row;
    }
  }
}

Er det en god ide?

Custom Properties

html {
  --primary: #5000ca;
  --secondary: #463866;
  --accent: #fb28a8;

  --space-xxs: 0.25rem;
  --space-xs: 0.5rem;
  --space-sm: 1rem;
  --space-md: 1.5rem;
  --space-lg: 2rem;
  --space-xl: 3rem;
  --space-xxl: 6rem;
}

eksempel

"Consistency"

Custom Properties

html {
  --space-sm: 1rlh; /* eller flydende */
  --space-lg: 2rlh; /* eller flydende */
}

.flow {
  > * + * {
    margin-top: var(--flow-space, var(--space-sm));
  }
  
  h2 {
    --flow-space: var(--space-lg);
  }
}

Flow-space

var(--value, 10px)

Custom Properties

Default værdi

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

@media (width > 40em) {
  .grid {
    --cols: 5;
  }
}

Custom Properties

Default værdi

.object-1 {
  --move-to: 100px;
}

.object-2 {
  --move-to: 400px;
}

[class|="object"] {
  animation: move 1s linear forwards;
}

@keyframes move {
  to {
    translate: var(--move-to);
  }
}

Custom Properties

SVG through data URL

:root {
	--svg: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">[SVG PATH]</svg>');
}

Custom Properties

Tema-opgaven

Custom Properties

dry

Loading...

Custom Properties

dry

Loading...

Custom Properties

Custom Properties

Boolean-ish

.rotate {
  --intent: 0;
  
  rotate: /* rotér v/ hover */ ;
  
  &:hover {
    --intent: 1;
  }
}

Brug calc() til at gange variablen med 45deg

0 * 45deg = 0deg

1 * 45deg = 45deg

Custom Properties

Boolean-ish

.slide-out {
  --open: 0;
  
  opacity: var(--open);
  translate: calc( var(--open) * 100% );
  transition:
    opacity .4s,
    translate .4s;
  
  &.open {
    --open: 1;
  }
}

Brug calc() til at gange variablen med 100%

Custom Properties

Boolean-ish

Linear Mapping

.element {
  --range: calc(0.6em * var(--p) + 0.2em);
  /*
  If --p is 0 --range is .2em
  If --p is 1 --range .8em
  */
}

Linear Mapping

Brug lineær mapping til at animere en variabel font ved musens bevægelse

Linear Mapping

Øvelse

p {
  color: green;
}

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

Kontekst

Kontekst

:root {
  --my-color: green;
}

aside {
  --my-color: red;
}

p {
  color: var(--my-color);
}
aside p {
  --my-color: red;
}

p {
  color: var(--my-color, green);
}

Kontekst

:root {
  --my-color: green;
}

aside {
  --my-color: red;
}

p {
  color: var(--my-color);
}
aside {
  --my-color: red;
}

p {
  color: var(--my-color, green);
}

Kontekst

@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

Style queries

Style queries

Tema-opgaven

Kontekst

Kontekst

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

[data-surface="dark"] {
  --surface-bg: var(--color-neutral-900);
}

.card {
  --card-bg: var(--color-neutral-700);

  @container style(--surface: dark) {
    --card-bg: var(--color-neutral-100);
  }
}
<section data-surface="dark">
  <!-- content -->
</section>

style queries

Kontekst

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

[data-surface="dark"] {
  --surface-bg: var(--color-neutral-900);
}

.card {
  --card-bg: var(--color-neutral-700);

  @container style(--surface: dark) {
    --card-bg: var(--color-neutral-100);
  }
}
<section data-surface="dark">
  <!-- content -->
</section>

style queries

Brug custom properties og style queries til lettere at organisere hver sektions farvetema samt indhold.

Themed sections

Øvelse

Lav øvelsen, der ligger på git-branch'en "scrolling-container" på GitHub

Scrolling Container

Øvelse

  1. Scroll-snap
  2. Invoker commands

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

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

pattern

max()

:root {
  --content-width: 75rem;
}

main {
  grid-template-columns:
    1fr minmax(0, var(--content-width)) 1fr;
}

.scroller {
  padding-inline:
    max(1rem, 50% - var(--content-width) / 2);
}

pattern

max()

section {
  padding-inline:
    max(1rem, 50% - var(--content-width) / 2);
}

Lav øvelsen, der ligger på git-branch'en "scrolling-container" på GitHub

Scrolling Container

Øvelse

NB! Du skal selv sætte knapperne ind

Crafting UI: Custom By Design

By Dannie Vinther

Crafting UI: Custom By Design

  • 155