Creazione di un componente Impostazioni

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

In questo post voglio condividere alcune riflessioni sulla creazione di un componente Impostazioni per il web che sia adattabile, supporti più input dei dispositivi e funzioni su tutti i browser. Prova la demo.

Demo

Se preferisci i video o vuoi un'anteprima della UI/UX di ciò che stiamo creando, 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. Input casella di controllo personalizzato
  5. Considerazioni sull'accessibilità
  6. JavaScript

Layout

Questa è la prima demo di GUI Challenge a essere interamente basata su CSS Grid. Ecco ogni griglia evidenziata con Chrome DevTools per la griglia:

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

Just for gap

Il layout più comune:

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

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

Cinque layout utilizzano questa strategia, eccoli tutti:

Layout di griglia verticale evidenziati con contorni e spazi vuoti riempiti

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

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

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

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

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

Ritorno a capo automatico della griglia

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

Centering wrapping content

Sia flexbox che griglia offrono la possibilità di align-items o align-content e, quando si tratta di elementi di wrapping, gli allineamenti del layout content distribuiscono lo spazio tra gli elementi secondari come gruppo.

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

L'elemento principale utilizza la proprietà abbreviata di allineamento place-content: center in modo che gli elementi secondari siano centrati verticalmente e orizzontalmente nei layout a una e due colonne.

Guarda nel video qui sopra come i "contenuti" rimangono centrati, anche se è stato eseguito l'andare a capo.

Ripeti minmax con adattamento automatico

<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 dare un tocco personalizzato al layout adattabile. Quando le colonne si impilano, vogliamo uno spazio ampio, ma non tanto quanto su uno schermo largo.

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

Se lo confronti con quello di Una, il nostro layout presenta tre aggiunte speciali:

  • Passiamo una funzione min() aggiuntiva.
  • Specifichiamo align-items: flex-start.
  • C'è uno stile max-width: 89vw.

La funzione min() aggiuntiva è descritta bene da Evan Minto nel suo blog nel post Intrinsically Responsive CSS Grid with minmax() and min(). Ti consiglio di leggerlo. La correzione dell'allineamento flex-start serve a rimuovere l'effetto di allungamento predefinito, in modo che gli elementi secondari di questo layout non debbano avere altezze uguali, ma possano avere altezze naturali e intrinseche. Il video di YouTube contiene una rapida analisi di questa aggiunta di allineamento.

max-width: 89vw merita una piccola analisi in questo post. Ecco il layout con e senza lo stile applicato:

Cosa succede? Quando viene specificato max-width, fornisce il contesto, il dimensionamento esplicito o il dimensionamento definito per l'algoritmo di layout auto-fit per sapere quante ripetizioni può inserire nello spazio. Anche se sembra ovvio che lo spazio sia "a larghezza intera", secondo la specifica della griglia CSS, è necessario fornire una dimensione o una dimensione massima definita. Ho fornito un valore max-size.

Quindi, perché 89vw? Perché "funzionava" 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

La maggior parte dell'armonia di questo layout deriva da una tavolozza limitata di spaziature, 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'utilizzo di questi flussi si integra perfettamente con la griglia, CSS @nest e la sintassi di livello 5 di @media. Ecco un esempio: il set di stili di layout completamente <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, con spaziatura interna moderata per impostazione predefinita (come sui dispositivi mobili). Tuttavia, man mano che lo spazio della viewport aumenta, si espande aumentando il padding. Il CSS del 2021 è davvero fantastico.

Ricordi il layout precedente, "solo per il gap"? Ecco una versione più completa di come appaiono 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 far risaltare questo design come espressivo, ma minimalista. Io faccio così:

: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);
}

Assegno ai colori della superficie e del testo dei numeri anziché nomi come surface-dark e surface-darker perché in una query supporti li invertirò e chiaro e scuro non avranno senso.

Le inverto in una query supporti per le preferenze 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);
    }
  }
}

È importante avere una rapida panoramica del quadro generale e della strategia prima di approfondire i dettagli della sintassi dei colori. Ma, dato che ho anticipato un po' i tempi, lasciatemi fare un passo indietro.

LCH?

Senza addentrarci troppo nella teoria dei colori, LCH è una sintassi orientata all'uomo, che si rivolge al modo in cui percepiamo il colore, non al modo in cui lo misuriamo con la matematica (come 255). Questo offre un netto vantaggio, in quanto gli esseri umani possono scriverlo più facilmente e altri esseri umani saranno in sintonia con questi aggiustamenti.

Screenshot della pagina web pod.link/csspodcast, con la puntata Color 2: Perception
Scopri di più sul colore percettivo (e non solo) nel CSS Podcast

Per oggi, in questa demo, concentriamoci sulla sintassi e sui valori che sto invertendo per creare la modalità Luce e Scura. Diamo un'occhiata a un colore di superficie e a un colore di 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) si traduce in 10% di luminosità, 0 di crominanza e 0 di tonalità: un grigio incolore molto scuro. Poi, nella query supporti per la modalità Luce, la luminosità viene invertita in 90% con --surface1: lch(90 0 0);. E questa è l'essenza della strategia. Inizia semplicemente modificando la luminosità tra i due temi, mantenendo i rapporti di contrasto richiesti dal design o ciò che può mantenere l'accessibilità.

Il vantaggio di lch() è che la luminosità è orientata all'essere umano e possiamo essere certi che una modifica di % sarà percettivamente e coerentemente diversa di %. Ad esempio, hsl() non è altrettanto affidabile.

Se ti interessa, puoi scoprire di più sugli spazi dei colori e su lch(). Sta arrivando!

Al momento, CSS non può accedere affatto a questi colori. Ripeto: non abbiamo accesso a un terzo dei colori nella maggior parte dei monitor moderni. E non si tratta di colori qualsiasi, ma dei colori più vivaci che lo schermo può visualizzare. I nostri siti web sono sbiaditi perché l'hardware dei monitor si è evoluto più velocemente delle specifiche CSS e delle implementazioni dei browser.

Lea Verou

Controlli dei moduli adattivi con combinazione di colori

Molti browser, attualmente Safari e Chromium, offrono controlli per il tema scuro, ma devi specificare nel CSS o nell'HTML che il tuo design li utilizza.

Quanto sopra mostra l'effetto della proprietà dal riquadro Stili di DevTools. La demo utilizza il tag HTML, che secondo me è generalmente una posizione migliore:

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

Scopri di più in questo color-scheme articolo di Thomas Steiner. C'è molto altro da guadagnare oltre alle caselle di controllo scure.

CSS accent-color

Di recente è stata introdotta l'attività relativa a accent-color sugli elementi dei moduli, ovvero un singolo stile CSS che può modificare il colore della tinta utilizzato nell'elemento di input del browser. Scopri di più qui su GitHub. L'ho incluso nei miei stili per questo componente. Poiché i browser lo supportano, le mie caselle di controllo saranno più in tema con i colori in risalto 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 focus-within

Il colore risalta di più quando viene utilizzato con parsimonia e uno dei modi in cui mi piace ottenere questo risultato è attraverso interazioni colorate con la UI.

Nel video qui sopra sono presenti molti livelli di feedback e interazione dell'interfaccia utente, che contribuiscono a dare personalità all'interazione:

  • Evidenziare il contesto.
  • Fornire un feedback della UI su "quanto è pieno" il valore nell'intervallo.
  • Fornire un feedback della UI che indica che un campo accetta l'input.

Per fornire un feedback quando si interagisce con un elemento, CSS utilizza la pseudo-classe :focus-within per modificare l'aspetto di vari elementi. Analizziamo .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 ha lo stato attivo:

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

Intervallo personalizzato

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

<input type="range">

Questo elemento è composto da tre parti che dobbiamo personalizzare:

  1. Elemento/contenitore dell'intervallo
  2. Track
  3. Thumb

Stili degli elementi 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 etichettarle chiaramente sia utile. Il resto degli stili sono per lo più stili di reimpostazione, per fornire una base coerente per la creazione delle parti più complesse del componente.

Stili di 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 consiste nel "rivelare" il colore di riempimento vivace. Questo viene fatto con il gradiente di interruzione brusca in alto. Il gradiente è trasparente fino alla percentuale di riempimento, dopodiché utilizza il colore della superficie della traccia non riempita. Dietro questa superficie non riempita, c'è un colore a larghezza intera, in attesa che la trasparenza lo riveli.

Stile riempimento traccia

Il mio design richiede JavaScript per mantenere lo stile di riempimento. Esistono strategie solo CSS, ma richiedono che l'elemento thumb abbia la stessa altezza della barra e non sono riuscito a 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 perfettamente senza JavaScript, la proprietà --track-fill non è obbligatoria, semplicemente non avrà uno stile di riempimento se non è presente. Se JavaScript è disponibile, compila la proprietà personalizzata e osserva anche eventuali modifiche dell'utente, sincronizzando la proprietà personalizzata con il valore.

Ecco un ottimo post su CSS-Tricks di Ana Tudor, che mostra una soluzione solo CSS per il riempimento della barra. Ho trovato molto stimolante anche questo elemento: range.

Stili dei cursori

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 serve a creare un bel cerchio. Anche in questo caso, puoi vedere il gradiente di sfondo fisso che unifica i colori dinamici dei cursori, delle tracce e degli elementi SVG associati. Ho separato gli stili per l'interazione per 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 creare un elemento visivo animato e facile da gestire per il feedback degli utenti. Utilizzando un'ombra della casella, posso evitare di attivare il layout con l'effetto. Per farlo, creo un'ombra non sfocata che corrisponde alla forma circolare dell'elemento thumb. Poi modifico e applico una transizione alla dimensione della diffusione al passaggio del mouse.

Se solo l'effetto di evidenziazione fosse così semplice sulle caselle di controllo…

Selettori cross-browser

Ho scoperto che avevo 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 ho personalizzato il suo aspetto:

<input type="checkbox">

Questo elemento è composto da tre parti che dobbiamo personalizzare:

  1. Elemento casella di controllo
  2. Etichette associate
  3. Effetto di 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 preparano lo pseudo-elemento che introdurremo in seguito per applicare lo stile all'evidenziazione. Per il resto, si tratta per lo più di piccole modifiche di stile da parte mia. Mi piace che il cursore sia un puntatore, mi piacciono gli offset del contorno, 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 delle caselle di controllo

È importante fornire etichette per le caselle di controllo per due motivi. La prima è rappresentare lo scopo del valore della casella di controllo, per rispondere alla domanda "Attivato o disattivato per cosa?". Il secondo riguarda la UX: gli utenti web si 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>

Nell'etichetta, inserisci un attributo for che rimandi a una casella di controllo tramite ID: <label for="text-notifications">. Nella casella di controllo, raddoppia sia il nome che l'ID per assicurarti che venga trovata con vari strumenti e tecnologie, come un mouse o uno screen reader: <input type="checkbox" id="text-notifications" name="text-notifications">. :hover, :active e altri sono inclusi senza costi con la connessione, aumentando i modi in cui è possibile interagire con il modulo.

Evidenziazione della casella di controllo

Voglio mantenere la coerenza delle mie interfacce e l'elemento slider ha una bella evidenziazione della miniatura che vorrei utilizzare con la casella di controllo. La miniatura è stata in grado di utilizzare box-shadow e la relativa proprietà spread per aumentare e diminuire le dimensioni di un'ombra. Tuttavia, questo effetto non funziona qui perché le nostre caselle di controllo sono, e dovrebbero essere, quadrate.

Sono riuscito a ottenere lo stesso effetto visivo con uno pseudo-elemento e una quantità sfortunata di CSS complicato:

@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 lavoro semplice, ma posizionarlo dietro l'elemento a cui è collegato è stato più difficile. Ecco il prima e il dopo la correzione:

È sicuramente una microinterazione, ma per me è importante mantenere la coerenza visiva. La tecnica di scalabilità dell'animazione è la stessa che abbiamo utilizzato in altri punti. Impostiamo una proprietà personalizzata su un nuovo valore e lasciamo che il CSS esegua la transizione in base alle preferenze di movimento. La funzionalità principale è translateZ(-1px). L'elemento padre ha creato uno spazio 3D e questo pseudo-elemento figlio lo ha sfruttato posizionandosi leggermente indietro nello spazio Z.

Accessibilità

Il video di YouTube mostra chiaramente le interazioni con mouse, tastiera e screen reader per questo componente delle impostazioni. Ecco alcuni dettagli.

Scelte degli elementi HTML

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

Ognuno di questi contiene suggerimenti e consigli per lo strumento di navigazione dell'utente. Alcuni elementi forniscono suggerimenti per l'interazione, altri collegano l'interattività e altri ancora contribuiscono a formare l'albero di accessibilità che uno screen reader naviga.

Attributi HTML

Possiamo nascondere gli elementi che non sono 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. Questo perché abbiamo nascosto l'icona che potrebbe essere stata un passaggio intermedio prima di arrivare al cursore successivo. Senza questo attributo, un utente dovrebbe fermarsi, ascoltare e andare oltre l'immagine che potrebbe non essere in grado di vedere.

L'SVG è un insieme di formule matematiche, aggiungiamo un elemento <title> per un titolo al passaggio del mouse senza costi e un commento leggibile dall'uomo su ciò che la formula matematica sta creando:

<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 un codice HTML sufficientemente chiaro, in modo che il modulo funzioni molto bene con mouse, tastiera, controller per videogiochi e screen reader.

JavaScript

Ho già spiegato come veniva gestito il colore di riempimento della barra da JavaScript, quindi diamo un'occhiata al codice JavaScript relativo 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 utilizzato 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 vengono visualizzati in una tabella

Conclusione

Ora che sai come ho fatto io, come faresti tu? Questo rende l'architettura dei componenti divertente. Chi realizzerà la prima versione con gli slot nel proprio framework preferito? 🙂

Diversifichiamo i nostri approcci e impariamo tutti i modi per creare sul web. Crea una demo, inviami i link su X e li aggiungerò alla sezione Remix della community di seguito.

Remix della community

  • @tomayac con il loro stile per l'area di passaggio del mouse per le etichette delle caselle di controllo. Questa versione non ha uno spazio di passaggio del mouse tra gli elementi: demo e origine.