Icons
Auf dieser Seite
icon() gibt ein inline <svg> zurück. Jedes Zeichen ist auf demselben 24×24-Raster als ungefüllte Striche in currentColor gezeichnet, erbt also Farbe und Schriftgröße von dem, worin es sitzt, und braucht keine eigenen Stile.
stack({ direction: 'row', gap: 'md', align: 'center' },
icon('check'),
icon('search'),
icon('trash'),
icon('settings'),
)In einer Komponente
Ein Icon ist ein Kind wie jedes andere. Weil es sich in em bemisst, passt es zum Label daneben, ohne dass man ihm dessen Größe sagen müsste:
stack({ direction: 'row', gap: 'sm', align: 'center', wrap: true },
button({ color: 'primary' }, icon('download'), 'Herunterladen'),
button({ variant: 'outline' }, icon('external-link'), 'Öffnen'),
button({ size: 'sm', variant: 'soft', color: 'danger' }, icon('trash'), 'Löschen'),
iconButton({ label: 'Suchen', variant: 'soft', icon: icon('search') }),
)Größe
Der Standard ist 1em — die Größe des umgebenden Textes. size nimmt ein Token oder jede CSS-Länge, wenn du davon abweichen willst:
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' }),
)Farbe
Es gibt keine Farb-Prop. Ein Icon wird in currentColor gezeichnet und nimmt damit die Farbe seines Kontexts an — genau das lässt einen Satz in fünf Paletten funktionieren:
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' })),
)Zugängliche Namen
Ein Icon ist standardmäßig aria-hidden, was weit häufiger richtig ist als nicht: ein Icon neben dem Wort „Löschen“ soll nicht ein zweites Mal angesagt werden. Gib ihm nur dann ein label, wenn das Icon die ganze Bedeutung trägt — dann wird es role="img" mit diesem Namen.
icon('trash') // dekorativ — versteckt
button(icon('trash'), 'Löschen') // das Wort spricht
icon('trash', { label: 'Löschen' }) // als Bild angesagt
// Ein Icon-Button beschriftet den Button, nicht das Zeichen darin
iconButton({ label: 'Löschen', icon: icon('trash') })Gefüllt
filled malt ein Zeichen aus, statt es zu umreißen. Es ist in beiden Fällen derselbe Pfad — nur das Attribut fill ändert sich —, sodass beide Formen exakt dieselbe Außenkante teilen und nicht auseinanderdriften können.
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' }),
)Und dieselben Namen, gefüllt:
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' }),
)Es ist eine Prop und kein zweiter Satz von Namen, weil der gefüllte Zustand fast immer ein Zustand ist — gespeichert, geliked, bewertet — und deshalb ein Boolean will, keinen anderen String:
icon('heart', { filled: liked })
icon('bookmark', { filled: saved, label: saved ? 'Gespeichert' : 'Speichern' })
// statt
icon(liked ? 'heart-filled' : 'heart')Die Status-Zeichen füllen sich anders, weil ihre Marke innerhalb der Form sitzt. Den Kreis auszumalen würde das Häkchen schlucken, also wird die Marke stattdessen ausgestanzt:
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' }),
)Diese tragen eine zweite Zeichnung — die volle Form mit der per fill-rule: evenodd herausgeschnittenen Marke —, weil sich eine solche Aussparung nicht durch Ändern eines Attributs aus dem Umrisspfad gewinnen lässt. Die äußere Form wird an der Außenkante des Umrisses gezeichnet, sodass beide Fassungen auf derselben Silhouette enden. Es ist in beiden Fällen dieselbe Prop; welchen Mechanismus ein Zeichen nutzt, ist seine eigene Sache.
Ein Chevron hat gar kein Inneres zum Ausmalen — es ist eine offene Linie — und füllt sich daher zu dem Dreieck, das seine drei Punkte beschreiben, samt dem Strich, der die Ecken rundet:
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() listet alles, was auf filled hört. Ein Zeichen ohne gefüllte Form ignoriert es und bleibt umrissen — eye zu füllen würde die Pupille verlieren und tag sein Loch, also tut keines von beiden so.
Drehen
spin dreht das Zeichen — gedacht für spinner, auch wenn dich nichts daran hindert, refresh zu drehen, während etwas neu lädt. Unter prefers-reduced-motion wird es zum Schneckentempo statt anzuhalten, denn ein stehender Spinner sieht kaputt aus.
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 }), 'Speichert…'),
)Der Satz
Namen beschreiben die Zeichnung, nicht ihren Zweck — x-circle, nicht error —, weil dieselbe Zeichnung für unzusammenhängende Zwecke benutzt wird und ein Name, der das Bild beschreibt, dabei wahr bleibt. Die Aliase unten decken die üblichen Absichten ab.
Marken
Acht Markenzeichen kommen mit dem Satz — facebook, google, instagram, linkedin, tiktok, whatsapp, x-twitter und youtube. Sie nehmen weiterhin size und label und zeichnen weiterhin 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'), 'Teilen'),
)Sie sind Reproduktionen fremder Marken und keine Zeichnungen im Stil dieser Bibliothek, brechen also absichtlich zwei ihrer Regeln: es sind volle Flächen statt Striche, denn genau das ist ein Logo, und ihre Proportionen sind die der Marke, nicht die dieses Rasters. filled bedeutet ihnen nichts — sie sind es bereits.
Die Zeichnungen stammen von Simple Icons, die sie unter CC0 veröffentlichen. Das deckt die Zeichnung ab, nicht die Marke: nutze sie, um auf das zu zeigen, was sie benennen — einen Profillink, einen Teilen-Button — und nicht auf einem eigenen Produkt.
Es heißt x-twitter, nicht x, weil x bereits ein Alias für close ist und ein Schließen-Button, der sich in ein Logo verwandelt, eine böse Überraschung wäre. twitter führt ebenfalls dorthin.
Aliase
Jeder davon rendert ein oben gelistetes Zeichen, unter dem Namen, zu dem du eher greifst:
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
Eigene Icons
registerIcons() fügt ein Zeichen hinzu oder ersetzt ein mitgeliefertes. Das Markup ist der Inhalt des <svg> — Formen auf demselben 24×24-Raster, ungefüllt gelassen, damit currentColor sie erreicht. Rufe es einmal aus einem Modul auf, das deine Seiten importieren:
import { registerIcons } from 'sitelo/ui'
registerIcons({
logo: '<path d="M4 20 12 4l8 16z"/>',
// Ein bereits vorhandener Name ersetzt es überall — so gestaltest du ein
// mitgeliefertes Zeichen um, ohne die Bibliothek zu forken.
check: '<path d="m5 13 4 4 10-11"/>',
// Eine geschlossene Form, damit es wie die mitgelieferten auf `filled` hört.
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') // dein Zeichen
icon('check') // jetzt ebenfalls deins
registerIcons({ check: null }) // und zurück zum mitgeliefertenWarum inline und kein Sprite
Icons werden in die Seite gerendert, statt aus einem icons.svg per <use> geholt zu werden. Ein Sprite spart in der Größenordnung von hundert gzippten HTML-Bytes pro Seite und kostet dafür einen Roundtrip — wiederholtes Markup ist genau der Fall, in dem gzip am besten ist, das meiste, wofür es ein Sprite gibt, ist also schon dedupliziert. Inline heißt außerdem: keine Datei zu erzeugen, kein Basispfad zu konfigurieren und nichts, das in dist fehlen kann — derselbe Handel, den styles({ inline: true }) eingeht.
Props
| Prop | Typ | Standard | Beschreibung |
|---|---|---|---|
name | string | — | Welches Zeichen. Kann stattdessen als erstes Argument übergeben werden. |
size | 'sm' | 'md' | 'lg' | string | 'md' | Ein Token oder jede CSS-Länge. Standard ist 1em. |
label | string | — | Als Bild mit diesem Namen ansagen, statt es zu verstecken. |
spin | boolean | false | Es fortlaufend drehen. |
filled | boolean | false | Das Zeichen ausmalen statt umreißen. Von nicht füllbaren Zeichen ignoriert. |
Ein unbekannter Name rendert gar nichts, statt zu werfen — eine kosmetische Prop soll keinen Build scheitern lassen können. hasIcon(name) sagt dir, ob es eines gibt, iconNames() listet sie alle, und fillableIcons() die, die filled annehmen.