Icone

icon() restituisce un <svg> inline. Ogni glifo è disegnato sulla stessa griglia 24×24 come tratti non riempiti in currentColor, quindi eredita il colore e la dimensione del carattere di ciò in cui sta e non ha bisogno di stile proprio.

stack({ direction: 'row', gap: 'md', align: 'center' },
  icon('check'),
  icon('search'),
  icon('trash'),
  icon('settings'),
)

Dentro un componente

Un’icona è un figlio come tutti gli altri. Poiché si dimensiona in em, si accorda all’etichetta che le sta accanto senza che le venga detto quanto è grande:

stack({ direction: 'row', gap: 'sm', align: 'center', wrap: true },
  button({ color: 'primary' }, icon('download'), 'Scarica'),
  button({ variant: 'outline' }, icon('external-link'), 'Apri'),
  button({ size: 'sm', variant: 'soft', color: 'danger' }, icon('trash'), 'Elimina'),
  iconButton({ label: 'Cerca', variant: 'soft', icon: icon('search') }),
)

Dimensione

Il valore predefinito è 1em — la dimensione del testo circostante. size accetta un token o qualunque lunghezza CSS quando vuoi staccartene:

stack({ direction: 'row', gap: 'md', align: 'center' },
  icon('star', { size: 'sm' }),
  icon('star'),
  icon('star', { size: 'lg' }),
  icon('star', { size: '2rem' }),
  icon('star', { size: '3rem' }),
)

Colore

Non esiste una prop per il colore. Un’icona è disegnata in currentColor, quindi prende il colore del proprio contesto — ed è questo che fa funzionare un solo insieme dentro cinque palette:

stack({ direction: 'row', gap: 'md', align: 'center' },
  text({ style: 'color: var(--su-primary)' }, icon('heart', { size: 'lg' })),
  text({ style: 'color: var(--su-success)' }, icon('check-circle', { size: 'lg' })),
  text({ style: 'color: var(--su-warning)' }, icon('alert-triangle', { size: 'lg' })),
  text({ style: 'color: var(--su-danger)' }, icon('x-circle', { size: 'lg' })),
  text({ tone: 'muted' }, icon('info', { size: 'lg' })),
)

Nomi accessibili

Un’icona è aria-hidden per impostazione predefinita, cosa giusta molto più spesso che no: un’icona accanto alla parola “Elimina” non dovrebbe essere annunciata una seconda volta. Dalle una label solo quando è l’icona a portare tutto il significato, e diventa role="img" con quel nome.

icon('trash')                      // decorativa — nascosta
button(icon('trash'), 'Elimina')   // è la parola a parlare

icon('trash', { label: 'Elimina' }) // annunciata come immagine

// Un pulsante di sola icona dà il nome al pulsante, non al glifo dentro
iconButton({ label: 'Elimina', icon: icon('trash') })

Riempita

filled dipinge un glifo invece di delinearlo. Il tracciato è lo stesso nei due casi — cambia solo l’attributo fill — quindi le due forme condividono esattamente il bordo esterno e non possono divergere.

stack({ direction: 'row', gap: 'md', align: 'center' },
  icon('bell', { size: 'lg' }),
  icon('bookmark', { size: 'lg' }),
  icon('folder', { size: 'lg' }),
  icon('heart', { size: 'lg' }),
  icon('star', { size: 'lg' }),
)

E gli stessi nomi riempiti:

stack({ direction: 'row', gap: 'md', align: 'center' },
  icon('bell', { filled: true, size: 'lg' }),
  icon('bookmark', { filled: true, size: 'lg' }),
  icon('folder', { filled: true, size: 'lg' }),
  icon('heart', { filled: true, size: 'lg' }),
  icon('star', { filled: true, size: 'lg' }),
)

È una prop e non un secondo insieme di nomi perché lo stato riempito è quasi sempre uno stato — salvato, piaciuto, valutato — quindi vuole un booleano, non una stringa diversa:

icon('heart', { filled: liked })
icon('bookmark', { filled: saved, label: saved ? 'Salvato' : 'Salva' })

// invece che
icon(liked ? 'heart-filled' : 'heart')

I glifi di stato si riempiono in modo diverso, perché il loro segno sta dentro la forma. Dipingere il cerchio inghiottirebbe la spunta, quindi il segno viene invece ritagliato fuori:

stack({ direction: 'row', gap: 'md', align: 'center' },
  icon('check-circle', { filled: true, size: 'lg' }),
  icon('x-circle', { filled: true, size: 'lg' }),
  icon('info', { filled: true, size: 'lg' }),
  icon('help', { filled: true, size: 'lg' }),
  icon('alert-triangle', { filled: true, size: 'lg' }),
)

Quelli portano un secondo disegno — la forma piena con il segno ritagliato da fill-rule: evenodd — perché un ritaglio non si può ottenere dal tracciato del contorno cambiando un attributo. La forma esterna è disegnata sul bordo esterno del contorno, così le due forme finiscono comunque sulla stessa sagoma. La prop è la stessa in entrambi i casi; quale meccanismo usi un glifo sono affari suoi.

Un chevron non ha alcun interno da dipingere — è una linea aperta — quindi si riempie fino al triangolo che descrivono i suoi tre punti, mantenendo il tratto che arrotonda gli angoli:

stack({ direction: 'row', gap: 'md', align: 'center' },
  icon('chevron-up', { filled: true, size: 'lg' }),
  icon('chevron-down', { filled: true, size: 'lg' }),
  icon('chevron-left', { filled: true, size: 'lg' }),
  icon('chevron-right', { filled: true, size: 'lg' }),
)

fillableIcons() elenca tutto ciò che risponde a filled. Un glifo senza forma riempita lo ignora e resta delineato — riempire eye perderebbe la pupilla e tag il suo foro, quindi nessuno dei due fa finta di niente.

Rotazione

spin fa ruotare il glifo — pensato per spinner, anche se nulla ti impedisce di far girare refresh mentre qualcosa si ricarica. Rallenta fino quasi a fermarsi invece di arrestarsi sotto prefers-reduced-motion, perché una rotella che si ferma sembra rotta.

stack({ direction: 'row', gap: 'md', align: 'center' },
  icon('spinner', { spin: true, size: 'lg' }),
  icon('refresh', { spin: true, size: 'lg' }),
  button({ variant: 'soft' }, icon('spinner', { spin: true }), 'Salvataggio…'),
)

L’insieme

I nomi descrivono il disegno più che il compito che svolge — x-circle, non error — perché lo stesso disegno viene usato per compiti senza relazione fra loro, e un nome che descrive l’immagine resta vero anche allora. Gli alias qui sotto coprono le intenzioni più comuni.

alert-triangle
arrow-down
arrow-left
arrow-right
arrow-up
banknote
bell
bold
bookmark
calendar
camera
check
check-circle
chevron-down
chevron-left
chevron-right
chevron-up
chevrons-left
chevrons-right
clock
close
code
coins
comment-bubble
copy
credit-card
database
download
edit
external-link
eye
eye-off
facebook
feather
file
filter
folder
gear
gift
globe
google
heart
help
home
image
info
instagram
italic
key
layers
link
linkedin
location
lock
log-in
log-out
mail
menu
minus
moon
more-horizontal
more-vertical
package
percent
pin
plus
print
receipt
refresh
search
settings
share
shopping-bag
shopping-cart
smartphone
sparkles
spinner
star
store
sun
tag
terminal
thumbs-down
thumbs-up
tiktok
trash
truck
universal-access
unlock
upload
user
users
video
wallet
whatsapp
x-circle
x-twitter
youtube
zap

Marchi

Con l’insieme arrivano otto marchi — facebook, google, instagram, linkedin, tiktok, whatsapp, x-twitter e youtube. Accettano comunque size e label e si disegnano comunque in currentColor:

stack({ direction: 'row', gap: 'md', align: 'center', wrap: true },
  icon('facebook', { size: 'lg' }),
  icon('instagram', { size: 'lg' }),
  icon('x-twitter', { size: 'lg' }),
  icon('youtube', { size: 'lg' }),
  icon('whatsapp', { size: 'lg' }),
  button({ variant: 'soft', color: 'neutral' }, icon('linkedin'), 'Condividi'),
)

Sono riproduzioni di marchi altrui e non disegni nello stile di questa libreria, quindi ne infrangono di proposito due regole: sono forme piene invece che tratti, che è quello che è un logo, e le loro proporzioni sono quelle del marchio e non di questa griglia. filled per loro non significa nulla — lo sono già.

Il disegno viene da Simple Icons, che lo rilascia sotto CC0. Questo copre il disegno, non il marchio registrato: usali per indicare la cosa che nominano — un link a un profilo, un pulsante di condivisione — e non su un prodotto tuo.

È x-twitter, non x, perché x è già un alias di close e un pulsante di chiusura che si trasforma in un logo sarebbe una brutta sorpresa. Anche twitter si risolve su di esso.

Alias

Ognuno di questi renderizza un glifo elencato qui sopra, sotto il nome a cui più probabilmente penseresti:

success → check-circle

warning → alert-triangle

danger, error → x-circle

x, cross → close

question → help

loading → spinner

cog, gears → gear

delete, trash-can → trash

pencil → edit

notification → bell

dots → more-horizontal

bolt, lightning → zap

arrow-back → arrow-left

arrow-forward → arrow-right

cart → shopping-cart

bag → shopping-bag

card → credit-card

cash, money → banknote

delivery, shipping → truck

shop → store

discount, sale → percent

login, sign-in → log-in

logout, sign-out → log-out

map-pin, marker → location

mobile → smartphone

like → thumbs-up

dislike → thumbs-down

comment, message, chat → comment-bubble

ai, magic → sparkles

printer → print

accessibility, a11y → universal-access

twitter → x-twitter

Icone tue

registerIcons() aggiunge un glifo, oppure ne sostituisce uno incorporato. Il markup è il contenuto dell’<svg> — forme sulla stessa griglia 24×24, lasciate non riempite così che currentColor le raggiunga. Chiamala una volta sola da un modulo che le tue pagine importano:

import { registerIcons } from 'sitelo/ui'

registerIcons({
  logo: '<path d="M4 20 12 4l8 16z"/>',
  // Un nome che esiste già lo sostituisce ovunque, ed è così che si
  // ridisegna un glifo incorporato senza forkare la libreria.
  check: '<path d="m5 13 4 4 10-11"/>',
  // Una sola forma chiusa, così può rispondere a `filled` come gli incorporati.
  pin: { markup: '<path d="M12 21s7-6.3 7-11a7 7 0 1 0-14 0c0 4.7 7 11 7 11z"/>', fillable: true },
})
import { icon } from 'sitelo/ui'

icon('logo')                  // il tuo glifo
icon('check')                 // ora anche questo è tuo

registerIcons({ check: null }) // e si torna a quello incorporato

Perché inline, e non uno sprite

Le icone vengono renderizzate dentro la pagina invece di essere prese da un icons.svg con <use>. Uno sprite fa risparmiare nell’ordine del centinaio di byte gzippati di HTML per pagina e costa un viaggio di rete per farlo — il markup ripetuto è esattamente il caso in cui gzip dà il meglio, quindi la maggior parte di ciò che uno sprite esiste per deduplicare è già stata deduplicata. Inline significa anche che non c’è alcun file da emettere, nessun percorso base da configurare, e niente che possa sparire da dist — lo stesso compromesso che fa styles({ inline: true }).

Props

PropTipoPredefinitoDescrizione
namestring—Quale glifo. Si può passare come primo argomento anziché come prop.
size'sm' | 'md' | 'lg' | string'md'Un token, o qualunque lunghezza CSS. Il predefinito è 1em.
labelstring—Annunciala come immagine con questo nome, invece di nasconderla.
spinbooleanfalseFalla ruotare di continuo.
filledbooleanfalseDipingi il glifo invece di delinearlo. Ignorata dai glifi che non si possono riempire.

Un nome sconosciuto non renderizza proprio nulla invece di sollevare un errore — una prop estetica non deve poter far fallire una build. hasIcon(name) ti dice se ne esiste uno, iconNames() li elenca tutti, e fillableIcons() quelli che accettano filled.