Salta al contenuto

Candela

Il design system come framework: due file, nessuna dipendenza, un attributo per ritemarlo su ogni progetto. Questa pagina è costruita interamente con le classi del framework.

candela.min.css · 47 kb candela.min.js · 15 kb namespace .cd- 7 temi accent ⌘K · comandi starter.html incluso font self-hosted
setup

Due righe, qualsiasi progetto

Copia candela.css, candela.js e la cartella fonts/ nel progetto. I font sono self-hosted e dichiarati dentro candela.css: nessuna chiamata a Google Fonts (conformità GDPR), copertura latina e cirillica inclusa.

<link rel="stylesheet" href="candela.css"> <script src="candela.js" defer></script> <!-- atmosfera: le sorgenti che il vetro sfoca --> <div class="cd-atmosphere" aria-hidden="true"> <div class="cd-orb cd-orb--a"></div> <div class="cd-orb cd-orb--b"></div> <div class="cd-orb cd-orb--c"></div> </div> <div class="cd-grain" aria-hidden="true"></div>
temi

Un attributo, sette identità

La sorgente calda è una sola variabile: --cd-accent-rgb. Tinte, glow, bottoni e checkbox si rigenerano da soli con color-mix(). Provali — l'intera pagina cambia:

<html data-accent="copper"> <!-- via HTML --> Candela.accent('copper') // via JS (persistito in localStorage) /* tema custom in una riga: */ [data-accent="mybrand"]{ --cd-accent-rgb: 120,200,255 }
materiale

Vetro · 4 elevazioni

L'altezza si misura in sfocatura: blur, opacità e bordo crescono insieme. g1 righe e celle · g2 card e pannelli · g3 nav e menu · g4 modali. Mai vetro dentro vetro.

.cd-g1

blur 12

.cd-g2

blur 20

.cd-g3

blur 32

.cd-g4

blur 48

<div class="cd-g2 cd-card">…</div> <div class="cd-g3 cd-r-lg">…</div> <!-- vetro + raggio a scelta -->
componenti

Bottoni

In hover si accendono, non si muovono: il primario passa da glow-2 a glow-3. Il caldo è l'azione del prodotto, il freddo la selezione dell'interfaccia.

<button class="cd-btn cd-btn--primary">Pubblica</button> <button class="cd-btn cd-btn--glass">Anteprima</button> <!-- varianti: --cool --ghost --danger · taglie: --sm --lg --icon · stato: is-loading (+ disabled) -->
componenti

Form

Il focus è sempre luce fredda; l'errore porta il proprio alone. Checkbox, radio e switch sono input nativi ridisegnati: funzionano nei form senza JS.

Il focus accende l'alone freddo.

IBAN incompleto: servono 27 caratteri.

<div class="cd-field"> <label class="cd-label" for="x">Email</label> <input class="cd-input" id="x" type="email"> <p class="cd-hint">Testo di aiuto</p> </div> <!-- stati sul wrapper: is-error · is-ok --> <label class="cd-choice"><input type="checkbox" class="cd-switch"> Notifiche</label>
componenti

Badge · Chip · Tag

Il punto luminoso rende lo stato leggibile a colpo d'occhio. I chip sono filtri selezionabili: attivi, prendono l'accent.

operativo in attesa errore nota tag neutro
<span class="cd-badge cd-badge--ok">operativo</span> <button class="cd-chip is-active">Tutti</button>
componenti

Card

La card in hover sale di 5px e guadagna glow: si avvicina alla luce. --flush azzera il padding per contenuti con media.

base

Card semplice

Contenitore g2 con hover.

Attico vista mare

Sanremo · 210 m² · terrazza

€ 1.450.000
<div class="cd-card cd-g2 cd-card--hover">…</div> <div class="cd-card cd-card--flush cd-g2"> <div class="cd-card__media"></div> <div class="cd-card__body">…</div> </div>
componenti

Tabs

Navigabili con le frecce, ARIA completo. La tab attiva prende accent + glow-1.

Contenuto della descrizione.

<div data-cd-tabs> <div class="cd-tabs cd-g1" role="tablist"> <button class="cd-tab" role="tab" aria-controls="p1" aria-selected="true">Uno</button> <button class="cd-tab" role="tab" aria-controls="p2" aria-selected="false">Due</button> </div> <div id="p1" class="cd-tabpanel" role="tabpanel">…</div> <div id="p2" class="cd-tabpanel" role="tabpanel" hidden>…</div> </div>
componenti

Accordion

<details> nativo ridisegnato: accessibile e indicizzabile. L'apertura e la chiusura sono animate — il click viene intercettato perché togliendo l'attributo open il browser nasconderebbe il contenuto all'istante. Apertura con ease-out, chiusura con ease-in-out, e senza JS resta il comportamento nativo.

Quanto costa la proposta?
La proposta d'acquisto non ha costi: si versa solo la caparra confirmatoria.
Tempi del rogito
In media 60–90 giorni dal preliminare, salvo mutuo.
<details class="cd-acc cd-g1"> <summary>Domanda</summary> <div class="cd-acc__body">Risposta.</div> </details>
componenti

Toast

API a una chiamata; il contenitore si crea da solo con aria-live.

Candela.toast({ title:'Salvato', msg:'Le modifiche sono online.', type:'ok', dur:4200 })
componenti

Tooltip

Solo CSS, da un attributo. Appare anche al focus da tastiera.

A4 ⓘ
<button class="cd-btn cd-btn--glass" data-cd-tip="Copia negli appunti">…</button>
componenti

Tabella

La riga selezionata usa la luce fredda: è una selezione dell'interfaccia, non un'azione del prodotto.

ImmobileZonaStatoPrezzo
Attico DuomoCentroattivo€ 1.450.000
Villa IrisCollinatrattativa€ 890.000
Bilocale PortoMarinasospeso€ 320.000
<div class="cd-table-wrap cd-g1"> <table class="cd-table">…</table> </div> <!-- riga evidenziata: <tr class="is-selected"> -->
componenti

Alert

AggiornamentoNuova versione del listino disponibile.
PubblicatoL'annuncio è ora visibile online.
Documenti mancantiManca l'APE per completare il fascicolo.
Pagamento rifiutatoVerifica i dati della carta e riprova.
<div class="cd-alert cd-alert--warn"> <div><b>Titolo</b><span class="cd-alert__txt">Dettaglio.</span></div> </div>
componenti

Progress · Spinner · Skeleton

Caricamento…
<div class="cd-progress"><i style="--value:64%"></i></div> <div class="cd-spinner"></div> <div class="cd-skel cd-skel--text"></div>
componenti

Avatar · Divider · Kbd

AD
AD
LZ
+3
Cerca con K
oppure
<div class="cd-avatar">AD</div> <div class="cd-divider">oppure</div> <span class="cd-kbd">⌘</span>
novità 3.0
motion

Reveal allo scroll

Un attributo fa entrare in scena l'elemento quando raggiunge la viewport; data-cd-stagger fa entrare i figli in sequenza. Con prefers-reduced-motion tutto appare fermo, e senza JS niente resta nascosto.

01

Entro per primo

02

Poi io

03

Per ultimo io

<section data-cd-reveal>…</section> <div class="cd-grid" data-cd-stagger>…figli in sequenza…</div>
motion

Spotlight

Un alone tenue segue il puntatore sulla superficie — questo pannello lo sta già facendo. Un solo listener delegato per tutta la pagina: aggiungi .cd-spot e basta.

Muovi il mouse qui

La luce ti segue.

Spot + hover

Si combinano senza conflitti.

<div class="cd-card cd-g2 cd-spot">…</div>
componenti

Statistiche animate

Il contatore parte quando entra in viewport, con formattazione italiana dei numeri. Il testo nel markup è il fallback senza JS.

214Immobili gestiti
98%Clienti soddisfatti
€ 1.450.000Volume mediato
<div class="cd-stat"> <b data-cd-count="1450000" data-cd-prefix="€ " data-cd-dur="1800">€ 1.450.000</b> <span>Volume mediato</span> </div>
componenti

Stepper

Processi e timeline: i passi completati portano l'accent, quello corrente riceve glow-2. Perfetto per checkout e iter di compravendita.

Proposta d'acquisto

Accettata il 12 marzo.

Preliminare

Registrato, caparra versata.

3

Istruttoria mutuo

In corso — delibera attesa entro 20 giorni.

4

Rogito

Da fissare dal notaio.

<div class="cd-steps"> <div class="cd-step is-done"><div class="cd-step__dot">✓</div><div><h3>Fatto</h3><p>Nota.</p></div></div> <div class="cd-step is-now"><div class="cd-step__dot">2</div><div><h3>In corso</h3></div></div> <div class="cd-step"><div class="cd-step__dot">3</div><div><h3>Prossimo</h3></div></div> </div>
componenti

Pricing

Il piano consigliato è l'unico che porta glow: la sorgente indica la scelta. Pronto per abbonamenti e listini.

base
€ 49 /mese
  • Sito one-page
  • Hosting incluso
  • Assistenza email
consigliato
€ 89 /mese
  • Sito multipagina
  • SEO + analytics
  • Aggiornamenti mensili
  • Assistenza prioritaria
studio
€ 149 /mese
  • Tutto di Pro
  • E-commerce
  • Gestione contenuti
<div class="cd-plan cd-g2 cd-plan--hot"> <span class="cd-tag">consigliato</span> <div class="cd-plan__price">€ 89<small> /mese</small></div> <ul><li>Voce inclusa</li></ul> <button class="cd-btn cd-btn--primary">Scegli</button> </div>
componenti

Drawer

Pannello laterale su <dialog> con uscita animata. È anche la nav mobile: sotto gli 860px nella barra in alto compare il burger, provalo restringendo la finestra.

<button class="cd-burger" data-cd-open="#menu" aria-label="Menu"><i></i><i></i><i></i></button> <dialog id="menu" class="cd-drawer"> <div class="cd-drawer__panel cd-g4"> <a class="cd-menu__item" href="#sezione" data-cd-close>Voce</a> <button class="cd-btn cd-btn--ghost" data-cd-close>Chiudi</button> </div> </dialog>
componenti

Command palette

Il gesto dei prodotti moderni: K (o Ctrl+K) apre la ricerca comandi — funziona adesso, su questa pagina. Filtro live, frecce per muoversi, Invio per eseguire. La selezione usa la luce fredda: è l'interfaccia che sceglie, non il prodotto.

<dialog class="cd-palette"> <div class="cd-palette__panel cd-g4"> <input class="cd-palette__input" placeholder="Cerca un comando…"> <div class="cd-palette__list"> <button class="cd-palette__item" onclick="…">Comando</button> <div class="cd-palette__empty" hidden>Nessun risultato.</div> </div> </div> </dialog> // apertura: Candela.palette() · scorciatoia ⌘K già inclusa
componenti

Empty state

Uno schermo vuoto è un invito ad agire, non un vicolo cieco.

Nessun immobile in archivio

Aggiungi il primo annuncio per vederlo comparire qui con foto e stato.

<div class="cd-empty"> <div class="cd-empty__ico">✦</div> <h4 class="cd-h4">Titolo</h4> <p>Cosa fare adesso.</p> <button class="cd-btn cd-btn--primary">Azione</button> </div>
novità 4.0
pattern

Hero + Source

La firma del sistema come componente riutilizzabile: la sorgente pulsante vista attraverso un pannello g3. Un solo .cd-source per pagina — è il glow-4, la sorgente.

la sorgente, dietro il vetro
<header class="cd-hero cd-wrap"> <div class="cd-source"> <div class="cd-source__core" aria-hidden="true"></div> <div class="cd-source__pane cd-g3"><span class="cd-mono">claim</span></div> </div> <h1 class="cd-display cd-display--glow">Titolo</h1> </header>
pattern

Marquee + Quote

La striscia scorre in loop (duplica il contenuto nel markup, in pausa all'hover) e la testimonianza porta il segno dell'accent. I bordi della striscia sfumano con una mask.

Hanno venduto il nostro attico in sei settimane, gestendo tutto: foto, visite, notaio.
MC
Maria C. · Venditrice
<div class="cd-marquee" aria-hidden="true"> <div class="cd-marquee__track"> <span>Voce</span><span>·</span>… <!-- poi duplica tutto per il loop --> </div> </div> <div class="cd-quote cd-g2"><blockquote>…</blockquote><footer>…</footer></div>
pattern

Footer

Griglia a colonne che collassa a due su mobile, con base legale separata. Vedi il markup completo in starter.html.

<footer class="cd-footer cd-wrap"> <div class="cd-footer__top"> <div class="cd-footer__brand"><span class="cd-nav__brand">Brand</span><p class="cd-small">…</p></div> <div class="cd-footer__col"><h3>Sito</h3><a href="#">Voce</a></div> </div> <div class="cd-footer__base"><span>© 2026</span><span>P.IVA</span></div> </footer>
architettura

Direzione

Il sistema funziona da destra a sinistra con un attributo e nient'altro. Nel foglio non ci sono più lati fisici: margin-inline-start al posto di margin-left, inset-inline-end al posto di right. Prova qui sotto: cambia solo il verso della pagina, non il markup.

Guarda la barra in alto, il torna-su, il pannello del menu e la freccia del select: si specchiano tutti. Il centraggio no, ed è giusto così.

<html lang="ar" dir="rtl"> <!-- non serve altro --> /* Nel CSS di progetto: mai un lato fisico. */ .mio-blocco{ margin-inline-start: 16px; inset-inline-end: 0; text-align: start } /* Per una traslazione che deve seguire il verso, il sistema espone un numero che vale 1 oppure -1. Serve perché una @keyframes non si può riscrivere sotto [dir="rtl"]. */ .mio-pannello{ transform: translateX(calc(100% * var(--cd-dir))) } /* Le due eccezioni volute: il centraggio, che si annulla in qualunque verso, e le forme costruite con i bordi. */ .centrato{ left: 50%; transform: translateX(-50%) }
architettura

Modalità

Tre assi indipendenti, tutti ad attributi. La densità stringe il ritmo per CRM e pannelli; l'inchiostro "abisso" scurisce tutto per gli OLED; e l'accent vale anche su un singolo contenitore — la card qui sotto è in copper mentre la pagina resta com'è.

data-accent="copper" solo qui

Sezione ritemata

Le variabili cascano dal contenitore: glow e bottoni seguono.

<html data-density="compact" data-ink="abisso"> <!-- globali --> <section data-accent="copper">…</section> <!-- accent locale -->
architettura

Validazione nativa

Zero JS: con :user-invalid l'errore compare solo dopo l'interazione, mai mentre scrivi la prima volta. Prova a inviare vuoto.

Diventa rossa solo dopo che l'hai toccata.

<input class="cd-input" type="email" required> <!-- lo stile di errore arriva da :user-invalid, senza classi né JS -->
comfort

Scrollspy · Skip · Torna su · Scroll lock

L'indice qui a fianco si sta già evidenziando da solo (data-cd-spy sul contenitore dei link). Premi Tab da inizio pagina per vedere lo skip link; il bottone torna-su è comparso in basso a destra; e quando una modale è aperta la pagina dietro non scorre più.

<a class="cd-skip" href="#contenuto">Salta al contenuto</a> <nav data-cd-spy><a href="#sezione">…</a></nav> <button class="cd-top" data-cd-top aria-label="Torna su">↑</button> /* lo scroll lock è automatico: html:has(dialog:modal){overflow:hidden} */
produzione

Starter

Nel pacchetto trovi starter.html: un sito completo già montato — skip link, nav con burger e drawer, hero con sorgente, marquee, card spotlight, statistiche, form validato, footer, cookie banner, torna-su e palette. Per un progetto nuovo: copia la cartella, apri lo starter, sostituisci i testi. In produzione usa le build minificate candela.min.css e candela.min.js.

progetto/ ├─ candela.min.css ← produzione ├─ candela.min.js ├─ starter.html → rinomina in index.html e personalizza └─ (candela.css / .js per lo sviluppo)
novità 5.0
dati

Grafici

SVG generati dal framework, zero dipendenze, un attributo con i valori. Si disegnano quando entrano in viewport; la temperatura segue le regole del sistema — accent per il prodotto, freddo/ok/bad per i segnali. --glow aggiunge l'alone.

Sparkline · accent + glow

Sparkline · cool

Barre

Anello

<div data-cd-spark="14,15,17,16,19,21,22,24" class="cd-chart--glow"></div> <div data-cd-bars="2,3,1,4,3,5,4,6" style="height:120px"></div> <div data-cd-ring="72" data-cd-text="18/25"></div> <!-- varianti: cd-chart--cool / --ok / --bad / --glow -->
applicazioni

App shell

La struttura per CRM e pannelli: sidebar sticky in vetro con voce attiva, topbar, contenuto. Sotto i 940px la sidebar sparisce e il burger apre un drawer. Vedila montata e funzionante in template-dashboard.html, insieme a KPI, grafici e tabella.

<div class="cd-app"> <aside class="cd-side"> <span class="cd-nav__brand">Brand</span> <a class="cd-side__link is-active" href="#">Dashboard</a> <a class="cd-side__link" href="#">Immobili</a> <div class="cd-side__foot">…utente…</div> </aside> <div> <header class="cd-topbar">…burger, ricerca, avatar…</header> <main class="cd-main">…</main> </div> </div>
flusso di lavoro

Kit AI

Nel pacchetto c'è CANDELA-PROMPT.md: le istruzioni da dare a Claude a inizio progetto. Contiene le regole non negoziabili, l'inventario completo delle classi, le API e i "non fare" — così ogni sessione di lavoro produce codice coerente col sistema invece di reinventare bottoni e ombre. Mettilo nella cartella del progetto e chiedi all'AI di leggerlo prima di scrivere una riga.

progetto/ ├─ candela.min.css · candela.min.js ├─ CANDELA-PROMPT.md ← "leggi questo prima di scrivere codice" ├─ starter.html → siti vetrina ├─ template-immobili.html → catalogo immobiliare ├─ template-menu.html → menu ristorante └─ template-dashboard.html → CRM e pannelli
novità 6.0
ristorazione & commercio

Dish + Quantità

La riga di menu con il prezzo a puntini — il classico dei menu, ridisegnato — e il selettore quantità per e-commerce e ordini. Il template template-menu.html monta il menu completo in copper.

Trofie al pesto di Prà€ 15

Con fagiolini e patate, come da tradizione.

glutinefrutta a guscio
Ravioli di branzino€ 19
<div class="cd-dish"> <span class="cd-dish__name">Piatto</span><span class="cd-dish__price">€ 15</span> <p class="cd-dish__desc">Descrizione.</p> <div class="cd-dish__tags"><span class="cd-tag">glutine</span></div> </div> <div class="cd-qty" data-cd-qty> <button data-cd-minus>−</button><input type="number" value="1" min="1"><button data-cd-plus>+</button> </div>
media

Prima/dopo + Video leggero

Il confronto trascinabile per ristrutturazioni e restyling, e la facade video: non contatta nessuno finché non premi. Fino alla 11.1 l'anteprima arrivava da i.ytimg.com al caricamento della pagina, prima di qualunque scelta; ora senza data-cd-poster la facciata si disegna da sola e scrive il dominio che il clic aprirà. L'iframe parte al click, su youtube-nocookie.com.

Prima Dopo prima dopo
<div class="cd-compare"> <img src="prima.jpg" alt="Prima"><img src="dopo.jpg" alt="Dopo"> <input type="range" min="0" max="100" value="50" aria-label="Confronta"> </div> <div data-cd-video="ID_YOUTUBE" data-cd-label="Guarda il video"></div>
tempo & condivisione

Countdown + Condividi

Il conto alla rovescia per open house, lanci e offerte; il bottone condividi usa la Web Share nativa su mobile e copia il link altrove.

<div data-cd-until="2026-12-31T18:00" data-cd-ended="Offerta terminata"></div> <button class="cd-btn cd-btn--glass" data-cd-share>Condividi</button>
produzione

Stampa in chiaro

Su carta il dark è uno spreco di inchiostro: con ⌘P il framework spegne atmosfera, vetro e glow da solo — sfondo bianco, testo scuro, niente bottoni né banner. Un menu o una scheda immobile si stampano bene senza fare nulla. Provalo su questa pagina o su template-menu.html.

/* già incluso nel framework: */ @media print{ body{background:#fff} …vetro→bianco, glow→niente, nav/bottoni nascosti }
7.0 · solidità
qualità

Verificato dal vivo

Questa versione non aggiunge componenti: corregge. Il framework è stato renderizzato in un browser reale e sottoposto ad audit. Le correzioni: i link dentro drawer e palette ora scrollano davvero alla destinazione dopo la chiusura (lo scroll-lock delle modali inghiottiva il salto all'ancora); la stampa mostra anche sezioni e grafici non ancora rivelati; il prima/dopo ha il focus visibile da tastiera; con trasparenza ridotta anche i backdrop di drawer, palette e lightbox diventano pieni; la rotellina resta contenuta dentro pannelli e menu; il countdown è localizzabile per i progetti in altre lingue.

Misurato (12.1): axe-core 4.13 — 0 violazioni su 112 misure: 9 pagine × 2 viewport (1280 e 390 px), ciascuna anche negli stati aperti — menu, vista della ricerca, modale, drawer, palette, lightbox, banner del consenso e pannello delle preferenze — più due misure da destra a sinistra per pagina. Zero elementi oltre il viewport a 320 px, e dalla 12.1 quello zero è dimostrato: il controllo veniva provato iniettando uno sbordo di 260 px, e finché il <body> contava fra i contenitori che ritagliano non poteva segnalarlo. Zero elementi focalizzabili ma invisibili. Zero errori a runtime, zero id doppi, zero riferimenti ARIA nel vuoto su tutte e nove le pagine, con gli overlay aperti (npm run integrita). Contrasto verificato sui pixel reali, non dedotto dal CSS, su 15 campioni × tutti e sette gli accent — 105 misure, minimo 4,72:1 contro una soglia di 4,5. Fino alla 12.0 il contrasto era misurato su una variante sola, e il marquee sopra l'orb stava a 4,05:1 senza che nessuno lo sapesse. Nessuna pagina contatta terzi al caricamento (npm run terzi), e nessuna regola larga scavalca un componente su 954 elementi distinti (npm run specificita). Si rimisura con npm run audit, contrast:tutti, integrita e specificita.

Perché gli stati contano: axe salta i sottoalberi visibility:hidden e inert, quindi su una pagina a riposo non vedrebbe mai il pannello del menu né gli overlay — misurare solo il caricamento statico avrebbe dato «0 violazioni» senza guardare dove sono i rischi. Fuori dalla portata degli strumenti, e da guardare a mano: forced-colors su Windows reale, l'ordine di lettura e la chiarezza delle etichette.

<div data-cd-until="2026-12-31T18:00" data-cd-labels="giorni,ore,min,sec"></div> <!-- default: giorni,ore,min,sec -->
8.0 · distribuzione & strumenti
distribuzione

Un repo, tutti i siti

La cartella è pronta per Git: LICENSE.txt, CHANGELOG.md e gli strumenti in tools/. Pubblicata su GitHub, jsDelivr la serve come CDN gratuito: correggi una volta, si aggiornano tutti i siti che puntano alla versione. I font viaggiano sul CDN con gli stessi percorsi relativi.

Sostituisci TUO-UTENTE col tuo nome utente GitHub. Copiato così com'è, l'URL restituisce 404: è un segnaposto, non un indirizzo reale.
<!-- Sviluppo locale --> <link rel="stylesheet" href="candela.css"> <!-- Produzione via CDN (repo pubblico) --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/TUO-UTENTE/candela@12.0.0/candela.min.css"> <script src="https://cdn.jsdelivr.net/gh/TUO-UTENTE/candela@12.0.0/candela.min.js" defer></script> # Pubblicazione, una volta sola: git init && git add -A && git commit -m "CANDELA 12.0" git tag v12.0.0 git remote add origin https://github.com/TUO-UTENTE/candela.git git push -u origin main --tags

Nel pacchetto anche gli strumenti: theme-builder.html genera il tema di un cliente con verifica del contrasto live e download del CSS; npm run build rigenera le versioni minificate, il bundle e lo specimen a partire dai sorgenti, e npm run check verifica che nessuna pagina usi classi cd- inesistenti.

8.3 · navigazione
11.0 · dati

Liste che filtrano davvero

Fino alla 10.10 chip, paginazione e tabella erano gusci: si vedevano e non facevano niente. Ora filtro e paginazione lavorano sullo stesso insieme, in quest'ordine — impaginare prima di filtrare impaginerebbe anche ciò che è stato escluso.

Attico vista mare
Rustico in collina
Bilocale sul lungomare

Nessun immobile con questi criteri.

<div class="cd-cluster" data-cd-filter="immobili"> <button class="cd-chip is-active" data-cd-value>Tutti</button> <!-- senza valore azzera --> <button class="cd-chip" data-cd-value="mare">Mare</button> </div> <div data-cd-list="immobili"> <article data-cd-tags="mare vendita">…</article> </div> <nav class="cd-pages" data-cd-pages="immobili" data-cd-per="6"></nav> <!-- vuoto: i bottoni li disegna il framework --> <div class="cd-empty cd-list-empty">…</div> // un criterio che il sistema non può conoscere (prezzo, data) Candela.list('immobili', { filter: el => +el.dataset.cdPrezzo < 300000 })

Poi: <th data-cd-sort> mette dentro un <button> vero e gestisce aria-sort — i numeri scritti all'italiana si ordinano da numeri, e data-cd-value sulla cella ordina per un valore diverso da quello mostrato. .cd-bulk[data-cd-bulk="#tabella"] conta le caselle, evidenzia le righe e gestisce lo stato indeterminato, parlando in luce fredda perché la selezione è interfaccia, non prodotto. .cd-range-dual è un intervallo a due maniglie che non si scavalcano. input.cd-file in .cd-file-drop accetta il trascinamento.

11.1 · 12.0 · segni

Segni d'interfaccia e marchi

Due famiglie, due attributi, una tabella sola. I segni sono disegnati con la grammatica del sistema — riquadro 24, tratto 1,6, ingombro ottico 17. I marchi sono i loghi veri: un logo ridisegnato per entrare in una famiglia resta un'approssimazione di quel logo.

<button data-cd-ico="cestino" aria-label="Elimina"></button> <a data-cd-social="instagram" href="…"></a> <!-- il nome accessibile lo mette il framework --> <html data-cd-marchi="colore"> <!-- i dischi di marca --> Candela.marchiStile('colore') <!-- oppure da JS; senza argomenti legge lo stato --> // aggiungerne di propri, prima di Candela.init() Candela.icons.pec = '<path d="…"/>'; Candela.marchi.spotify = { g:'<path d="…"/>', d:['#1ed760','#1db954'], n:'Spotify' };

I quindici marchi hanno due facce: il glifo solo, che prende il colore del testo, e il badge col disco di marca. Il default è mono e non per prudenza — quindici dischi saturi su un fondo scuro sono quindici buchi di luce che si contendono l'attenzione col contenuto. A colori hanno senso dove sono il contenuto. I quattro segni di contatto si accendono col tuo accent, così in una fila mista non resta metà accesa e metà spenta.

11.3 · consenso

Il consenso che blocca davvero

Un banner che memorizza un booleano ed emette un evento non impedisce niente: è un filtro che non filtra. Il valore del componente non sta nel banner — sta nella primitiva che impedisce alle risorse di partire.

<!-- le risorse di terzi non partono finché la finalità non è concessa --> <iframe data-cd-consent="media" data-cd-src="https://…" data-cd-ratio="4/3"></iframe> <img data-cd-consent="media" data-cd-src="https://…" width="800" height="600"> <script type="text/plain" data-cd-consent="statistiche" data-cd-src="…/analytics.js"></script> <!-- il banner: due tasti con la STESSA classe, e un link all'informativa --> <div class="cd-cookie cd-cookie--fix cd-g3" data-cd-cookie="sito" data-cd-policy="2026-08" data-cd-purposes="statistiche media"> <p>… <a href="/cookie-policy">Informativa</a></p> <button class="cd-btn cd-btn--glass cd-btn--sm" data-cd-accept>Accetta tutto</button> <button class="cd-btn cd-btn--glass cd-btn--sm" data-cd-decline>Rifiuta tutto</button> <button class="cd-btn cd-btn--ghost cd-btn--sm" data-cd-prefs>Preferenze</button> </div> <a href="#" data-cd-consent-open="sito">Preferenze cookie</a> <!-- nel footer --> // «ora o mai, ma una volta sola»: copre TUTTI E DUE i rami Candela.onConsent('statistiche', avviaAnalytics)

Accetta e rifiuta portano la stessa classe. Un rifiuto che pesa meno dell'assenso è la violazione più sanzionata d'Europa — ed è contro la regola 3 di questo sistema: l'accent è del prodotto, e «accetta i cookie» non è il prodotto. Al posto di una risorsa bloccata compare un segnaposto che nomina il terzo, ed è quell'informazione a rendere il clic una scelta invece di un riflesso. Lo sblocco è a caldo: nessun ricaricamento, nessuna posizione di scorrimento persa.

Il record porta la versione dell'informativa (mai quella del framework, o pubblicare la 12.1 rifarebbe la domanda a tutti) e una scadenza, 180 giorni di default. Un consenso vecchio in formato 11.2 viene migrato a statistiche e basta: i media di terzi non erano nemmeno nominati nel testo di allora, e un consenso che nessuno ha mai dato non si inventa. E va detto senza giri: senza un registro lato server non hai una prova, hai uno stato.

12.0 · forme

Combobox, data, segmentato, tooltip

I pezzi che mancavano perché il sistema reggesse un modulo qualunque.

<div class="cd-combo" data-cd-combo="citta"> <input class="cd-input" placeholder="Cerca…" aria-label="Città"> <div class="cd-combo__lista cd-g3"> <button class="cd-combo__voce" data-valore="sr">Sanremo</button> </div> </div> <!-- l'input[type=hidden] col nome dato lo crea il framework --> <input class="cd-input" type="date"> <!-- nessuna classe in più --> <div class="cd-seg" role="group" aria-label="Periodo"> <label><input type="radio" name="p" checked><span>Mese</span></label> </div>

Sotto il combobox c'è un <input> vero: la tastiera del telefono, il riempimento automatico e l'invio del modulo funzionano — la maggior parte delle implementazioni in giro sono un <div role="combobox"> e li perdono tutti e tre. Il segmentato ha radio veri sotto: frecce, gruppo e lettura arrivano dal browser. Il tooltip data-cd-tip dalla 12.0 vive nel livello alto: dentro un contenitore con overflow:hidden non sparisce più, e ai bordi rientra.

12.0 · testo

Testo lungo e documento

Ogni sito ha una pagina che il sistema non copriva: il chi siamo, un articolo, le note legali. Quel testo esce da un CMS come tag nudi, e il framework aveva stili solo per le classi — quindi o si annotava ogni tag a mano, o la pagina più testuale del sito era l'unica senza tipografia.

Un titolo dentro la prosa

Misura di lettura a 66 caratteri, e il distacco sopra un titolo è maggiore di quello sotto: così il titolo appartiene al testo che introduce, non a quello che chiude.

È l'unico punto del sistema che stila i tag invece delle classi, ed è voluto.
<article class="cd-prosa">…contenuto dal CMS, non annotato…</article> <div class="cd-chiaro">…una fattura, un preventivo, una scheda da stampare…</div>

CANDELA è scuro per costruzione: il principio è una sorgente di luce dietro il vetro, e vetro, bagliore e atmosfera discendono dal fondo scuro. Non c'è un tema chiaro e non è una mancanza — un CANDELA chiaro sarebbe un altro design system. Resta però il documento, e .cd-chiaro capovolge i token su un ramo: stessi nomi, altri valori, niente vetro (che senza una sorgente dietro sarebbe latte). Nessun componente sa di essere in chiaro e nessuno va riscritto.

12.0 · accessibilità

Quello che il framework scrive da sé

Una barra di avanzamento senza ruolo è un <div> decorativo per axe: l'audit diceva «0 violazioni» mentre il componente era muto. È la stessa categoria del fuoco invisibile che la 10.10 aveva inseguito — un difetto che nessuno strumento segnala.

Il primo pannello.
Il secondo.
<!-- bastano le classi: role, aria-controls, aria-labelledby e hidden li scrive il framework --> <div data-cd-tabs> <div><button class="cd-tab">Uno</button><button class="cd-tab">Due</button></div> <div class="cd-tabpanel">…</div><div class="cd-tabpanel">…</div> </div> Candela.loading(bottone, true) // aria-busy + aria-disabled, e il fuoco RESTA sul bottone

Il framework mette da sé: role="progressbar" coi valori su cd-progress — e nessun aria-valuenow quando la barra è indeterminata, perché dirlo è meglio che mentire con uno zero; role="status" sullo spinner; aria-describedby fra .cd-field e il suo .cd-hint; tabindex e role="region" sulle regioni scorrevoli; aria-current sull'ultima briciola; e l'ARIA completa dei tab.

Candela.loading() esiste perché l'istruzione «is-loading sempre insieme a disabled» era sbagliata: disabled toglie il fuoco al bottone appena premuto, e chi naviga da tastiera perde il posto nella pagina.

8.9 · chiusura
interazione

Un solo punto di chiusura

Modali, drawer, palette e lightbox non hanno più pulsanti propri: quando uno di essi si apre, la pillola si contrae nella × e chiude quello. L'utente impara una volta sola dove si chiude, e la × è sempre nello stesso punto dello schermo. Aprendo un overlay il menu si chiude da solo, così c'è sempre un solo livello e nessuna ambiguità.

Il nodo tecnico vale la pena di conoscerlo: le <dialog> modali vivono nel top layer del browser, che vince su qualunque z-index — una pillola lasciata nel body finirebbe sotto e sarebbe incliccabile. Perciò non viene sollevata: viene spostata dentro il dialog aperto e riportata a casa alla chiusura. Essendo position:fixed non si muove di un pixel, ma diventa visibile, cliccabile e inclusa nel focus trap nativo. Per lo stesso motivo le animazioni d'ingresso sono passate dal dialog al pannello interno: un transform sul dialog renderebbe la pillola relativa a lui.

Nelle pagine senza menu a pillola il framework crea da sé una × identica e la rimuove alla chiusura: la chiusura universale non dipende dalla presenza del menu.

<dialog class="cd-modal"> <div class="cd-modal__panel cd-g4">…</div> </dialog> <!-- nessun pulsante di chiusura: ci pensa la × della pillola --> Candela.closeTop() // chiude l'overlay in cima Candela.overlayOpen() // c'è qualcosa di aperto?
9.8 · footer
g4 · sospeso

Conferma pubblicazione

L'annuncio sarà visibile su tutti i portali collegati. Potrai ritirarlo in qualsiasi momento.

Candela
Setup Temi Vetro Bottoni Form Motion Pricing Palette ⌘K
tema

Preferenze di consenso

Puoi cambiare idea in qualunque momento: questa finestra si riapre dal collegamento «Preferenze cookie» nel footer.