Creare un componente Impostazioni

Una panoramica di base su come creare un componente delle impostazioni di dispositivi di scorrimento e caselle di controllo.

In questo post vorrei condividere pensare alla creazione di un componente delle impostazioni per il web che sia reattivo, supporti più input dei dispositivi e funzioni su tutti i browser. Prova la demo.

Demo

Se preferisci i video o vuoi un'anteprima UI/UX di ciò che stiamo realizzando, ecco una breve procedura dettagliata su YouTube:

Panoramica

Ho suddiviso gli aspetti di questo componente nelle seguenti sezioni:

  1. Layout
  2. Colore
  3. Inserimento intervallo personalizzato
  4. Inserimento casella di controllo personalizzata
  5. Considerazioni sull'accessibilità
  6. JavaScript

Layout

Questa è la prima demo della GUI Challenge a essere all CSS Grid. Ecco ogni griglia evidenziata con Chrome DevTools per griglia:

Contorni colorati e overlay spaziatura negli spazi che aiutano a mostrare tutte le caselle che compongono il layout delle impostazioni

Solo per lo spazio

Il layout più comune:

foo {
  display: grid;
  gap: var(--something);
}

Definisco questo layout "solo per gli spazi vuoti" perché utilizza solo la griglia per aggiungere spazi tra i blocchi.

Questa strategia è utilizzata da cinque layout. Eccoli tutti:

Layout griglia verticali evidenziati con contorni e riempiti con spazi vuoti

L'elemento fieldset, che contiene ogni gruppo di input (.fieldset-item), utilizza gap: 1px per creare i bordi sottili tra gli elementi. Nessuna complicata soluzione per i bordi.

Spazio vuoto
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
Trucco dei bordi
.grid {
  display: grid;

  & > .fieldset-item {
    background: var(--bg-surface-2);

    &:not(:last-child) {
      border-bottom: 1px solid var(--bg-surface-1);
    }
  }
}

Wrapping di griglia naturale

Il layout più complesso è stato quello delle macro, il sistema di layout logico tra <main> e <form>.

Centrare i contenuti a capo

Sia Flexbox che griglia offrono la possibilità di align-items o align-content e, quando si gestiscono gli elementi a capo, gli allineamenti del layout content distribuiranno lo spazio tra gli elementi secondari come gruppo.

main {
  display: grid;
  gap: var(--space-xl);
  place-content: center;
}

L'elemento principale utilizza l'accorciatoia di allineamento place-content: center in modo che gli elementi secondari siano centrati verticalmente e orizzontalmente in entrambi i layout a una e due colonne.

Nel video sopra, guarda il video in cui i "contenuti" rimangono centrati, anche se si è verificato il wrapping.

Ripeti adattamento automatico minmax

L'elemento <form> utilizza un layout a griglia adattivo per ogni sezione. Questo layout passa da una a due colonne in base allo spazio disponibile.

form {
  display: grid;
  gap: var(--space-xl) var(--space-xxl);
  grid-template-columns: repeat(auto-fit, minmax(min(10ch, 100%), 35ch));
  align-items: flex-start;
  max-width: 89vw;
}

Questa griglia ha un valore diverso per row-gap (--space-xl) rispetto a column-gap (--space-xxl) per aggiungere quel tocco personalizzato al layout adattabile. Quando le colonne sono impilate, vuoi uno spazio vuoto, ma non tanto grande come se fossimo su uno schermo ampio.

La proprietà grid-template-columns utilizza tre funzioni CSS: repeat(), minmax() e min(). Una Kravets ha un ottimo post del blog di layout in merito, chiamato RAM.

Ci sono tre aggiunte speciali nel nostro layout, se lo confronti con uno di Una:

  • Passiamo un'ulteriore funzione min().
  • Precisiamo align-items: flex-start.
  • È disponibile uno stile max-width: 89vw.

La funzione min() aggiuntiva è ben descritta da Evan Minto nel suo blog nel post Intrinsically Adaptive CSS Grid with minmax() and min(). Ti consiglio di leggerla. La correzione dell'allineamento flex-start consente di rimuovere l'effetto di stretching predefinito, in modo che gli elementi secondari di questo layout non devono avere la stessa altezza, ma possono avere altezze naturali e intrinseche. Il video di YouTube presenta una rapida suddivisione dell'aggiunta all'allineamento.

max-width: 89vw vale una piccola suddivisione in questo post. Vediamo il layout con e senza lo stile applicato:

Che cosa succede? Quando max-width è specificato, fornisce contesto, dimensionamento esplicito o dimensioni definite per l'algoritmo di layout auto-fit per sapere quante ripetizioni può rientrare nello spazio. Sebbene sembri ovvio che lo spazio è a "larghezza intera", secondo le specifiche della griglia CSS, è necessario specificare una dimensione definita o una dimensione massima. Ho fornito una dimensione massima.

Perché scegliere 89vw? Perché "ha funzionato" per il mio layout. Io e un paio di altre persone di Chrome stiamo indagando sul motivo per cui un valore più ragionevole, come 100vw, non è sufficiente e se si tratta effettivamente di un bug.

Spaziatura

Gran parte dell'armonia di questo layout deriva da una tavolozza limitata di spazi, 7 per l'esattezza.

:root {
  --space-xxs: .25rem;
  --space-xs:  .5rem;
  --space-sm:  1rem;
  --space-md:  1.5rem;
  --space-lg:  2rem;
  --space-xl:  3rem;
  --space-xxl: 6rem;
}

L'uso di questi flussi è molto conforme con la sintassi griglia, CSS @nest e sintassi di livello 5 di @media. Ecco un esempio, l'insieme di stili di layout completo <main>.

main {
  display: grid;
  gap: var(--space-xl);
  place-content: center;
  padding: var(--space-sm);

  @media (width >= 540px) {
    & {
      padding: var(--space-lg);
    }
  }

  @media (width >= 800px) {
    & {
      padding: var(--space-xl);
    }
  }
}

Una griglia con contenuti centrati, moderatamente riempita per impostazione predefinita (come sui dispositivi mobili). Tuttavia, man mano che diventa disponibile più spazio per l'area visibile, questo aumenta aumentando la spaziatura interna. Il CSS 2021 ha un aspetto davvero notevole.

Ricordi il layout precedente, "solo per il divario"? Di seguito è riportata una versione più completa del loro aspetto in questo componente:

header {
  display: grid;
  gap: var(--space-xxs);
}

section {
  display: grid;
  gap: var(--space-md);
}

Colore

Un uso controllato del colore ha contribuito a mettere in risalto questo design come espressivo, ma allo stesso tempo minimalista. Procedi nel seguente modo:

:root {
  --surface1: lch(10 0 0);
  --surface2: lch(15 0 0);
  --surface3: lch(20 0 0);
  --surface4: lch(25 0 0);

  --text1: lch(95 0 0);
  --text2: lch(75 0 0);
}

I nomi delle superfici e del testo vengono denominati con numeri anziché con nomi come surface-dark e surface-darker, poiché in una query supporti li invertirò, pertanto gli elementi chiari e scuri non saranno significativi.

Le cerco in una query multimediale preferita come questa:

:root {
  ...

  @media (prefers-color-scheme: light) {
    & {
      --surface1: lch(90 0 0);
      --surface2: lch(100 0 0);
      --surface3: lch(98 0 0);
      --surface4: lch(85 0 0);

      --text1: lch(20 0 0);
      --text2: lch(40 0 0);
    }
  }
}

Prima di addentrarci nei dettagli della sintassi per i colori, è importante dare una rapida occhiata al quadro generale e alla strategia. Ma dato che sono un po' avanti rispetto a me, fammi riprendere un po' più da vicino.

LCH?

Se non analizziamo troppo nel dettaglio la teoria del colore, LCH è una sintassi orientata all'uomo, che si adatta alla nostra percezione del colore, non al modo in cui misuriamo il colore con la matematica (ad esempio 255). Questo gli conferisce un vantaggio distintivo, in quanto gli esseri umani possono scriverlo più facilmente e gli altri saranno in linea con queste modifiche.

Uno screenshot della pagina web pod.link/csspodcast, con l&#39;episodio di Colore 2: Perception
Scopri di più sulla percezione del colore (e altro ancora) nel podcast CSS

Per oggi, in questa demo concentriamoci sulla sintassi e sui valori che eseguo per rendere chiari e scuri. Diamo un'occhiata a una superficie e un colore del testo:

:root {
  --surface1: lch(10 0 0);
  --text1:    lch(95 0 0);

  @media (prefers-color-scheme: light) {
    & {
      --surface1: lch(90 0 0);
      --text1:    lch(40 0 0);
    }
  }
}

--surface1: lch(10 0 0) corrisponde alla luminosità 10%, alla crominanza 0 e alla tonalità 0: un grigio molto scuro incolore. Poi, nella query supporti per la modalità Luce, la luminosità viene capovolta a 90% con --surface1: lch(90 0 0);. E questo è il concetto della strategia. Inizia semplicemente cambiando la leggerezza tra i due temi, mantenendo i rapporti di contrasto richiesti dal design o che possono mantenere l'accessibilità.

Il vantaggio di lch() in questo caso è che la leggerezza è orientata all'essere umano e possiamo essere soddisfatti di una modifica del %, che sarà percepita come % diversa. hsl(), ad esempio, non è così affidabile.

C'è altro da scoprire sugli spazi colore e su lch() se ti interessa. Sta per arrivare

Al momento CSS non può accedere a questi colori. Ripeto: non abbiamo accesso a un terzo dei colori dei monitor più moderni. Non si tratta solo di un qualsiasi colore, ma dei colori più vividi che lo schermo può mostrare. I nostri siti web sono sbiaditi perché l'hardware del monitor si è evoluto più velocemente delle specifiche CSS e delle implementazioni dei browser.

Lea Verou

Controlli dei moduli adattivi con schema colori

Molti browser offrono controlli con tema scuro, attualmente Safari e Chromium, ma devi specificare in CSS o HTML che il tuo design li utilizzi.

Quanto riportato sopra mostra l'effetto della proprietà nel riquadro Stili di DevTools. La demo utilizza il tag HTML, che a mio parere è generalmente una posizione migliore:

<meta name="color-scheme" content="dark light">

Scopri tutto in questo color-scheme articolo di Thomas Steiner. C'è molto altro da guadagnare rispetto all'input delle caselle di controllo.

CSS accent-color

C'è stata attività recente intorno a accent-color sugli elementi del modulo, ovvero un singolo stile CSS in grado di modificare il colore di tinta utilizzato nell'elemento di input del browser. Scopri di più qui su GitHub. L'ho incluso nei miei stili per questo componente. Man mano che i browser la supportano, le caselle di controllo saranno più incentrate sul tema, con i colori rosa e viola.

input[type="checkbox"] {
  accent-color: var(--brand);
}

Uno screenshot di Chromium su Linux con caselle di controllo rosa

Colori in risalto con gradienti fissi e messa a fuoco all'interno

I colori risaltano di più se utilizzati con parsimonia. Uno dei modi che mi piace per raggiungerlo è l'interazione colorata con l'interfaccia utente.

Nel video sopra riportato ci sono molti livelli di feedback e interazione sull'interfaccia utente, che contribuiscono a dare personalità all'interazione tramite:

  • Mettere in evidenza il contesto.
  • Fornisce un feedback nell'interfaccia utente sul valore compreso nell'intervallo.
  • Fornisce un feedback nell'interfaccia utente che indica che un campo accetta l'input.

Per fornire un feedback quando si interagisce con un elemento, il CSS utilizza la pseudoclasse :focus-within per modificare l'aspetto dei vari elementi. Analizziamo meglio la .fieldset-item. È molto interessante:

.fieldset-item {
  ...

  &:focus-within {
    background: var(--surface2);

    & svg {
      fill: white;
    }

    & picture {
      clip-path: circle(50%);
      background: var(--brand-bg-gradient) fixed;
    }
  }
}

Quando uno degli elementi secondari di questo elemento è attivo all'interno di:

  1. Allo sfondo .fieldset-item viene assegnato un colore di superficie con contrasto più elevato.
  2. L'elemento svg nidificato è riempito in bianco per un maggiore contrasto.
  3. L'elemento <picture> clip-path nidificato si espande in un cerchio intero e lo sfondo è riempito con un gradiente fisso luminoso.

Intervallo personalizzato

Dato il seguente elemento di input HTML, ti mostrerò come ne ho personalizzato l'aspetto:

<input type="range">

Questo elemento è composto da tre parti che dobbiamo personalizzare:

  1. Elemento intervallo / contenitore
  2. Track
  3. Pollice

Stili elemento dell'intervallo

input[type="range"] {
  /* style setting variables */
  --track-height: .5ex;
  --track-fill: 0%;
  --thumb-size: 3ex;
  --thumb-offset: -1.25ex;
  --thumb-highlight-size: 0px;

  appearance: none;         /* clear styles, make way for mine */
  display: block;
  inline-size: 100%;        /* fill container */
  margin: 1ex 0;            /* ensure thumb isn't colliding with sibling content */
  background: transparent;  /* bg is in the track */
  outline-offset: 5px;      /* focus styles have space */
}

Le prime righe di CSS sono le parti personalizzate degli stili e spero che etichettarli chiaramente ti aiuti. Gli altri stili sono per lo più reimpostati, per fornire una base coerente per la creazione delle parti difficili del componente.

Stili traccia

input[type="range"]::-webkit-slider-runnable-track {
  appearance: none; /* clear styles, make way for mine */
  block-size: var(--track-height);
  border-radius: 5ex;
  background:
    /* hard stop gradient:
        - half transparent (where colorful fill we be)
        - half dark track fill
        - 1st background image is on top
    */
    linear-gradient(
      to right,
      transparent var(--track-fill),
      var(--surface1) 0%
    ),
    /* colorful fill effect, behind track surface fill */
    var(--brand-bg-gradient) fixed;
}

Il trucco è "rivelare" il vivace colore di riempimento. Per farlo, inserisci il gradiente di interruzione fisso in alto. Il gradiente è trasparente fino alla percentuale di riempimento, dopodiché utilizza il colore della superficie della traccia inevasa. Dietro la superficie inevasa c'è un colore a larghezza intera, in attesa che venga rivelato dalla trasparenza.

Stile di riempimento della traccia

Il mio design richiede JavaScript per mantenere lo stile di riempimento. Esistono solo strategie CSS, ma richiedono che l'elemento thumb abbia la stessa altezza della traccia e non ho potuto trovare un'armonia entro questi limiti.

/* grab sliders on page */
const sliders = document.querySelectorAll('input[type="range"]')

/* take a slider element, return a percentage string for use in CSS */
const rangeToPercent = slider => {
  const max = slider.getAttribute('max') || 10;
  const percent = slider.value / max * 100;

  return `${parseInt(percent)}%`;
};

/* on page load, set the fill amount */
sliders.forEach(slider => {
  slider.style.setProperty('--track-fill', rangeToPercent(slider));

  /* when a slider changes, update the fill prop */
  slider.addEventListener('input', e => {
    e.target.style.setProperty('--track-fill', rangeToPercent(e.target));
  })
})

Penso che questo sia un bel miglioramento visivo. Il cursore funziona benissimo senza JavaScript. Il propulsore --track-fill non è obbligatorio, semplicemente non avrà uno stile di riempimento se non è presente. Se JavaScript è disponibile, compila la proprietà personalizzata osservando anche le modifiche dell'utente, sincronizzando la proprietà personalizzata con il valore.

Ecco un ottimo post su CSS-Tricks di Ana Tudor in cui viene presentata una soluzione esclusiva CSS per il riempimento della traccia. Ho trovato molto stimolante questo elemento range.

Stili pollici

input[type="range"]::-webkit-slider-thumb {
  appearance: none; /* clear styles, make way for mine */
  cursor: ew-resize; /* cursor style to support drag direction */
  border: 3px solid var(--surface3);
  block-size: var(--thumb-size);
  inline-size: var(--thumb-size);
  margin-top: var(--thumb-offset);
  border-radius: 50%;
  background: var(--brand-bg-gradient) fixed;
}

La maggior parte di questi stili è per creare un bel cerchio. Anche in questo caso è visibile il gradiente di sfondo fisso che unifica i colori dinamici di pollici, tracce e elementi SVG associati. Ho separato gli stili per l'interazione in modo da isolare la tecnica box-shadow utilizzata per l'evidenziazione al passaggio del mouse:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

::-webkit-slider-thumb {
  …

  /* shadow spread is initally 0 */
  box-shadow: 0 0 0 var(--thumb-highlight-size) var(--thumb-highlight-color);

  /* if motion is OK, transition the box-shadow change */
  @media (--motionOK) {
    & {
      transition: box-shadow .1s ease;
    }
  }

  /* on hover/active state of parent, increase size prop */
  @nest input[type="range"]:is(:hover,:active) & {
    --thumb-highlight-size: 10px;
  }
}

L'obiettivo era una facile gestione e un'evidenziazione animata per il feedback degli utenti. Utilizzando un'ombra della casella posso evitare di attivare il layout con l'effetto. Per farlo, creo un'ombra che non sia sfocata e che corrisponda alla forma circolare dell'elemento pollice. Quindi modifico e faccio la transizione della dimensione di diffusione al passaggio del mouse.

Se solo l'effetto di evidenziazione fosse stato così facile sulle caselle di controllo...

Selettori tra browser

Ho scoperto di aver bisogno di questi selettori -webkit- e -moz- per ottenere la coerenza tra i browser:

input[type="range"] {
  &::-webkit-slider-runnable-track {}
  &::-moz-range-track {}
  &::-webkit-slider-thumb {}
  &::-moz-range-thumb {}
}

Casella di controllo personalizzata

Dato il seguente elemento di input HTML, ti mostrerò come ne ho personalizzato l'aspetto:

<input type="checkbox">

Questo elemento è composto da tre parti che dobbiamo personalizzare:

  1. Elemento Casella di controllo
  2. Etichette associate
  3. Effetto evidenziazione

Elemento casella di controllo

input[type="checkbox"] {
  inline-size: var(--space-sm);   /* increase width */
  block-size: var(--space-sm);    /* increase height */
  outline-offset: 5px;            /* focus style enhancement */
  accent-color: var(--brand);     /* tint the input */
  position: relative;             /* prepare for an absolute pseudo element */
  transform-style: preserve-3d;   /* create a 3d z-space stacking context */
  margin: 0;
  cursor: pointer;
}

Gli stili transform-style e position si preparano allo pseudo-elemento che introdurremo più avanti per definire lo stile dei momenti salienti. Per il resto, sono per lo più contenuti di stile di minore entità da parte mia. Mi piace che il cursore sia il cursore, mi piacciono gli offset dei contorni, le caselle di controllo predefinite sono troppo piccole e, se accent-color è supportato, inserisci queste caselle di controllo nella combinazione di colori del brand.

Etichette caselle di controllo

È importante fornire etichette per le caselle di controllo per due motivi. Il primo è rappresentare per cosa viene utilizzato il valore della casella di controllo, per rispondere "on o off per cosa?" Il secondo riguarda l'UX: gli utenti web sono abituati a interagire con le caselle di controllo tramite le etichette associate.

input
<input
  type="checkbox"
  id="text-notifications"
  name="text-notifications"
>
etichetta
<label for="text-notifications">
  <h3>Text Messages</h3>
  <small>Get notified about all text messages sent to your device</small>
</label>

Sull'etichetta, inserisci un attributo for che rimanda a una casella di controllo in base all'ID: <label for="text-notifications">. Nella casella di controllo, raddoppia il nome e l'ID per assicurarti che vengano trovati con strumenti e tecnologie diversi, ad esempio un mouse o uno screen reader: <input type="checkbox" id="text-notifications" name="text-notifications">. :hover, :active e altri ancora vengono forniti senza costi con la connessione, aumentando le modi in cui è possibile interagire con il modulo.

Evidenziazione della casella di controllo

Voglio che le mie interfacce siano coerenti e l'elemento cursore presenta una miniatura in evidenza che voglio utilizzare con la casella di controllo. La miniatura ha potuto utilizzare box-shadow e la sua proprietà spread per scalare un'ombra verso l'alto e verso il basso. Tuttavia, questo effetto non funziona in questo caso perché le nostre caselle di controllo e dovrebbero essere quadrate.

Sono riuscita a ottenere lo stesso effetto visivo con uno pseudo elemento e una pessima quantità di CSS complicati:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

input[type="checkbox"]::before {
  --thumb-scale: .01;                        /* initial scale of highlight */
  --thumb-highlight-size: var(--space-xl);

  content: "";
  inline-size: var(--thumb-highlight-size);
  block-size: var(--thumb-highlight-size);
  clip-path: circle(50%);                     /* circle shape */
  position: absolute;                         /* this is why position relative on parent */
  top: 50%;                                   /* pop and plop technique (https://web.dev/centering-in-css#5-pop-and-plop) */
  left: 50%;
  background: var(--thumb-highlight-color);
  transform-origin: center center;            /* goal is a centered scaling circle */
  transform:                                  /* order here matters!! */
    translateX(-50%)                          /* counter balances left: 50% */
    translateY(-50%)                          /* counter balances top: 50% */
    translateZ(-1px)                          /* PUTS IT BEHIND THE CHECKBOX */
    scale(var(--thumb-scale))                 /* value we toggle for animation */
  ;
  will-change: transform;

  @media (--motionOK) {                       /* transition only if motion is OK */
    & {
      transition: transform .2s ease;
    }
  }
}

/* on hover, set scale custom property to "in" state */
input[type="checkbox"]:hover::before {
  --thumb-scale: 1;
}

Creare uno pseudo-elemento circolare è un'operazione semplice, ma posizionarlo dietro l'elemento a cui è collegato era più difficile. Ecco prima e dopo la correzione:

È sicuramente un'interazione micro, ma per me è importante mantenere la coerenza visiva. La tecnica di scalabilità dell'animazione è la stessa che utilizzavamo in altri luoghi. Impostiamo un nuovo valore per una proprietà personalizzata e lasciamo che sia CSS a trasferirla in base alle preferenze di movimento. La funzionalità principale è translateZ(-1px). Il padre ha creato uno spazio 3D e questo pseudo-elemento figlio l'ha sfruttato posizionandosi leggermente all'indietro in uno spazio z.

Accessibilità

Il video di YouTube mostra un'ottima dimostrazione delle interazioni di mouse, tastiera e screen reader per questo componente delle impostazioni. Riporto alcuni dettagli qui.

Scelta dell'elemento HTML

<form>
<header>
<fieldset>
<picture>
<label>
<input>

Ciascuno di questi blocchi contiene suggerimenti e suggerimenti per lo strumento di navigazione dell'utente. Alcuni elementi forniscono suggerimenti di interazione, altri collegano l'interattività e altri aiutano a dare forma all'albero di accessibilità esplorato da uno screen reader.

Attributi HTML

Possiamo nascondere gli elementi non necessari per gli screen reader, in questo caso l'icona accanto al cursore:

<picture aria-hidden="true">

Il video sopra mostra il flusso dello screen reader su Mac OS. Nota come lo stato attivo dell'input si sposta direttamente da un cursore all'altro. perché abbiamo nascosto l'icona che potrebbe essere stata un'interruzione del passaggio al cursore successivo. Senza questo attributo, un utente dovrebbe fermarsi, ascoltare e andare oltre l'immagine che potrebbe non essere in grado di vedere.

Il file SVG è un calcolo matematico: aggiungiamo un elemento <title> per ottenere un titolo senza costi per il passaggio del mouse e un commento leggibile sul significato dell'elemento matematico:

<svg viewBox="0 0 24 24">
  <title>A note icon</title>
  <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
</svg>

A parte questo, abbiamo utilizzato una quantità sufficiente di codice HTML contrassegnato in modo chiaro per fare in modo che il modulo funzioni bene su mouse, tastiera, controller per videogiochi e screen reader.

JavaScript

Ho già parlato di come il colore di riempimento della traccia veniva gestito da JavaScript, quindi diamo ora un'occhiata al codice JavaScript correlato a <form>:

const form = document.querySelector('form');

form.addEventListener('input', event => {
  const formData = Object.fromEntries(new FormData(form));
  console.table(formData);
})

Ogni volta che il modulo viene interagito e modificato, la console lo registra come oggetto in una tabella per facilitarne la revisione prima dell'invio a un server.

Uno screenshot dei risultati di console.table(), in cui i dati del modulo sono visualizzati in una tabella.

Conclusione

Ora che sai come ci sono riuscito, come faresti?! Questo rende l'architettura dei componenti divertente. Chi realizzerà la prima versione con slot nel framework preferito? 🙂

Diversifica i nostri approcci e scopriamo tutti i modi per creare sul web. Crea una demo, inviami un tweet contenente i link e lo aggiungerò alla sezione Remix della community di seguito.

Remix della community

  • @tomayac con lo stile relativo all'area al passaggio del mouse per le etichette delle caselle di controllo. Questa versione non ha intervalli di passaggio del mouse tra gli elementi: demo e source.