Skip to content

Designsystem

Die Designsprache, aus der ranui gebaut ist, und der vollständige Katalog der Tokens, die sie ausdrücken: jede globale --ran-*-Custom-Property, die die Bibliothek deklariert, samt ihrem Wert in beiden Themes. Komponenten lesen diese Tokens, statt Werte festzuschreiben — ein Token zu überschreiben gestaltet also alles um, was es verwendet.

Drei Seiten beantworten drei verschiedene Fragen, und sie sind bewusst getrennt:

SeiteBeantwortet
Designsystem (diese Seite)Was die Tokens sind: das Vokabular
GestaltungsleitlinienWie man wählt, wenn man eine Oberfläche baut
ThemengestaltungWie man sie zur Laufzeit umschaltet und überschreibt

Einsetzen, wenn du den Namen oder den Wert eines Tokens brauchst (eine Farbrolle, eine Abstandsstufe, eine Symbolgröße, eine Schattenstufe, eine Beschleunigungskurve) oder verstehen willst, warum die Skalen so geformt sind, wie sie sind.

Die Sprache: Geist

Die Tokens von ranui beruhen auf Geist, dem quelloffenen Designsystem von Vercel. Jede Farbskala ist eine Leiter fester Aufgaben, eine je Sprosse, kein Vorrat an Tönen zur Auswahl: Sprosse 200 ist nicht „ein etwas dunkleres Grau“, sie ist „der Hintergrund beim Überfahren“. Steht die Aufgabe einer Sprosse fest, ist die Farbwahl für einen Interaktionszustand ein Nachschlagen, keine Ermessensfrage.

ranui übernimmt diese Leiter als seine --ran-*-Skalen, legt semantische Tokens darüber und liefert Geist Sans / Geist Mono als Standardschriften mit.

Zwei Ebenen

Ebene 1: die Basispalette. Die rohen Skalen weiter unten. Selten unmittelbar verwendet.

Ebene 2: die semantischen Tokens. --ran-color-* und Verwandte, auf Ebene 1 abgebildet. Verwende diese Ebene. Der Dunkelmodus definiert nur Ebene 1 neu, jedes semantische Token wechselt also über var() mit — ohne eine einzige Dunkel-Sonderregel je Komponente irgendwo in der Bibliothek.

--ran-gray-1000        →  #171717 (hell)   /  #ededed (dunkel)   ← Ebene 1, wechselt
--ran-color-text       →  var(--ran-gray-1000)                    ← Ebene 2, folgt
--ran-btn-color        →  var(--ran-color-text, …)                ← Komponenten-Token

Diese Kette ist die ganze Architektur: Ändere eine Basissprosse, und es wirkt überall; ändere ein semantisches Token, und es ändert eine Rolle; ändere ein Komponenten-Token, und es ändert ein Element.

Farbe

Die Leiter

Jede Farbtonskala läuft von 100 bis 1000, und jede Sprosse hat genau eine Aufgabe:

SprosseRolleSprosseRolle
100Standardhintergrund600Rahmen im gedrückten Zustand
200Hintergrund beim Überfahren700Deckende Füllung (Schaltfläche/Abzeichen)
300Hintergrund beim Drücken800Deckende Füllung (Überfahren)
400Standardrahmen900Sekundärer Text und Symbole
500Rahmen beim Überfahren1000Primärer Text und Symbole

Hintergründe

TokenHellDunkelWofür
--ran-background-100 #ffffff #000000Seitenhintergrund
--ran-background-200 #fafafa #000000Dezente Zonen der Seite

Grau — --ran-gray-100..1000

Die Skala hinter Text, Rahmen und Flächen.

SprosseHellDunkel
100 #f2f2f2 #1a1a1a
200 #ebebeb #1f1f1f
300 #e6e6e6 #292929
400 #eaeaea #2e2e2e
500 #c9c9c9 #454545
600 #a8a8a8 #878787
700 #8f8f8f #8f8f8f
800 #7d7d7d #7d7d7d
900 #4d4d4d #a0a0a0
1000 #171717 #ededed

Grau mit Alpha — --ran-gray-alpha-100..1000

Durchscheinend, legt sich also über jede Fläche: die richtige Wahl für einen Schleier, einen Hover-Hauch oder eine Trennlinie, die auf unbekanntem Inhalt liegen muss.

SprosseHellDunkel
100 #0000000d #ffffff12
200 #00000015 #ffffff17
300 #0000001a #ffffff21
400 #00000014 #ffffff24
500 #00000036 #ffffff3d
600 #0000003d #ffffff82
700 #00000070 #ffffff8a
800 #00000082 #ffffff78
900 #000000b3 #ffffff9c
1000 #000000e8 #ffffffeb

Blau — --ran-blue-100..1000

Reserviert für Links und den Fokusring.

SprosseHellDunkel
100 #f0f7ff #06193a
200 #e9f4ff #022248
300 #dfefff #002f62
400 #cae7ff #003674
500 #94ccff #00418b
600 #48aeff #0090ff
700 #006bff #006efe
800 #0059ec #005be7
900 #005ff2 #47a8ff
1000 #002359 #eaf6ff

Rot — --ran-red-100..1000

Gefahr und Fehler.

SprosseHellDunkel
100 #ffeeef #330a11
200 #ffe8ea #440d13
300 #ffe3e4 #5d0e17
400 #ffd7d6 #6f101b
500 #ffb1b3 #88151f
600 #ff676d #f32e40
700 #fc0035 #f13242
800 #ea001d #e2162a
900 #d8001b #ff565f
1000 #47000c #ffe9ed

Bernstein — --ran-amber-100..1000

Warnungen.

SprosseHellDunkel
100 #fff6de #2a1700
200 #fff4cf #361900
300 #fff1c1 #502800
400 #ffdc73 #5b3000
500 #ffc543 #703e00
600 #ffa600 #ed9a00
700 #ffae00 #ffae00
800 #ff9300 #ff9300
900 #aa4d00 #ff9300
1000 #561900 #fff3d5

Grün — --ran-green-100..1000

Erfolg.

SprosseHellDunkel
100 #ecfdec #002608
200 #e5fce7 #00320b
300 #d3fad1 #003a0e
400 #b9f5bc #004615
500 #82eb8d #006717
600 #4ce15e #00952d
700 #28a948 #00ac3a
800 #279141 #009432
900 #107d32 #00ca50
1000 #003a00 #d8ffe4

Semantische Farbtokens

Die Ebene, die Komponenten tatsächlich lesen. Alles hier löst sich über die Skalen oben auf und wechselt daher von selbst mit dem Theme.

TokenLöst auf zuRolle
--ran-color-bg--ran-background-100Seitenhintergrund
--ran-color-bg-subtle--ran-background-200Dezente Zonen der Seite
--ran-color-bg-elevated--ran-background-100 · gray-100 (dunkel)Karten, Flächen
--ran-color-bg-muted--ran-gray-100Eingesenkte / gedämpfte Füllungen
--ran-color-bg-hover--ran-gray-200Fläche beim Überfahren
--ran-color-bg-active--ran-gray-300Fläche beim Drücken
--ran-color-text--ran-gray-1000Haupttext
--ran-color-text-secondary--ran-gray-900Sekundärer Text
--ran-color-text-disabled--ran-gray-700Deaktivierter Text
--ran-color-border--ran-gray-400Standardrahmen
--ran-color-border-secondary--ran-gray-300Dezenterer Rahmen
--ran-color-border-hover--ran-gray-500Rahmen beim Überfahren
--ran-color-border-active--ran-gray-600Rahmen beim Drücken
--ran-color-primary--ran-gray-1000Die Hauptaktion (monochrom)
--ran-color-primary-hover #383838 · #cccccc (dunkel)Primär beim Überfahren
--ran-color-primary-active #4d4d4d · #b3b3b3 (dunkel)Primär beim Drücken
--ran-color-primary-text--ran-background-100Die Tinte auf einer primären Fläche
--ran-color-success--ran-green-700Erfolg
--ran-color-warning--ran-amber-700Warnung
--ran-color-danger--ran-red-700Gefahr / Fehler
--ran-color-link--ran-blue-700Links

--ran-color-primary-hover / -active sind die beiden Literale der semantischen Ebene: Sie bewegen sich auf den Seitenhintergrund zu statt entlang einer Skala, deshalb definiert der Dunkelmodus sie unmittelbar neu.

Was jeder Akzent bedeutet

  • Primär ist monochrom: schwarz auf weiß im Hellen, weiß auf schwarz im Dunklen (der Markenton von Geist, <r-button type="primary">). Text und Symbole darauf nutzen --ran-color-primary-text, das mitwechselt. Ein eigenes „Kontrast“-Token gibt es nicht: Primär ist die Aktion mit dem höchsten Kontrast.
  • Blau ist reserviert für Links (--ran-color-link) und den Fokusring. Es ist kein alternatives Primär.
  • Grün = Erfolg · Bernstein = Warnung · Rot = Gefahr. Je eine Bedeutung.

Es gibt kein --ran-color-error; das Token heißt --ran-color-danger. Ein var(), das eine nie deklarierte Eigenschaft nennt, löst sich zu nichts auf, und die ganze Deklaration fällt stillschweigend weg — deshalb lohnt es, einen falschen Namen an dieser Tabelle zu prüfen statt zu raten.

Abstand

Die Zwischenräume zwischen Dingen: padding, margin, gap. Eine Basiseinheit von 4px mit neun Werten, mehr nicht:

TokenWertTokenWert
--ran-space-14px--ran-space-832px
--ran-space-28px--ran-space-1040px
--ran-space-312px--ran-space-1664px
--ran-space-416px--ran-space-2496px
--ran-space-624px

Die Zahl ist das Vielfache von 4px, die Skala springt also: Ein --ran-space-5 gibt es nicht. Genau darum geht es: Eine begrenzte Auswahl erzeugt den Rhythmus einer Seite.

Größen

Die eigenen Maße eines Elements: Symbolgrößen, Höhen von Bedienelementen, kleine quadratische oder rechteckige Steuerelemente.

TokenWertTypischerweise
--ran-size-116pxKästchen einer Checkbox, kleines Symbol im Fließtext
--ran-size-218px
--ran-size-320pxSymbol innerhalb eines Bedienelements
--ran-size-424pxSymbolschaltfläche in einer Werkzeugleiste
--ran-size-528pxHöhe eines kompakten Bedienelements
--ran-size-630px
--ran-size-732pxStandardhöhe eines Bedienelements

Das ist mit Absicht eine eigene Skala neben dem Abstand, und beide zu mischen ist ein maschinell geprüfter Fehler (sizing-scale). Die beiden haben unterschiedliche Bereiche und Abstufungen (eine Abstandsskala, die ab 4px verdoppelt, ergibt für Symbol- und Bedienelementgrößen ungeschickte Werte), und wer sie verwendet, muss die eine nachjustieren können, ohne die andere zu stören: Ein größer werdendes Symbol soll nicht zugleich jeden Zwischenraum verbreitern, der zufällig denselben Pixelwert teilt. Wo eine Sprosse zahlenmäßig mit einer Abstandsstufe zusammenfällt (--ran-size-4 und --ran-space-6 sind beide 24px), ist das Zufall, kein Alias.

Ein wirklich einmaliges Maß, das keine andere Komponente teilt (etwa das min-width eines Menüs), bleibt ein schlichtes Komponenten-Token mit eigenem literalen Rückfallwert, statt in eine Stufe gezwungen zu werden.

Typografie

TokenWert
--ran-font-familyGeist / Geist Sans, danach der System-UI-Stapel
--ran-font-monoGeist Mono, danach ui-monospace, SF Mono, Menlo, Consolas, …
--ran-font-size14px (die Basisgröße)
--ran-line-height1.5715

Schrift ist nach Rolle geordnet, und die Rolle legt Schriftart, Größe, Stärke und Zeilenhöhe gemeinsam fest:

RolleVerwendungStärke-TokenGrößen-Tokens
headingÜberschriften--ran-text-heading-weight (600)--ran-text-heading-1..4 (32/24/20/16px)
labelEinzeilig, zum Überfliegen--ran-text-label-weight (500)--ran-text-label-1..3 (14/13/12px)
copyMehrzeiliger Fließtext--ran-text-copy-weight (400)--ran-text-copy-1..2 (16/14px)
buttonText auf Schaltflächen--ran-text-button-weight (500)--ran-text-button-size (14px)
monoCode, Daten, Dachzeilen--ran-text-mono-weight-regular (400) / --ran-text-mono-weight-medium (500)übernimmt die Größen von label / copy

Zwei Tokens gibt es nur, damit eine Rolle richtig sitzt:

TokenWertWarum
--ran-text-heading-tracking-0.03emÜberschriften brauchen in großen Graden eine engere Laufweite.
--ran-text-button-line-height1Sauberes vertikales Zentrieren in einem Bedienelement mit fester Höhe.

Geist deckelt die Stärke bei 600 (Semibold). Betonung kommt aus Größe und Abstand, nicht aus einem fetteren Schnitt. Ein --ran-text-copy-3 gibt es nicht: Die 12px-Stufe heißt --ran-text-label-3.

Schriften

ranui hostet beide Schnitte selbst (variable Stärke 100–900, SIL OFL 1.1), ein einziger Import lädt sie also ohne CDN-Abhängigkeit:

js
import 'ranui/fonts'; // Bundler
html
<link rel="stylesheet" href="…/ranui/dist/fonts/fonts.css" />

Ohne ihn fallen die Tokens auf die Schriftstapel des Systems zurück; alles funktioniert weiter, nur ohne die Geist-Schnitte.

Radius

TokenWertWofür
--ran-radius-sm6pxBedienelemente: Schaltfläche, Eingabefeld, Auswahl
--ran-radius-md12pxKarten, Dialoge
--ran-radius-lg16pxGroße Flächen
--ran-radius-full9999pxPillenformen, Profilbilder

Elevation

Schatten ist eine Rolle, keine Dekoration. Wähle die Stufe danach, was das Element ist. Der Dunkelmodus ersetzt alle drei, denn ein für eine weiße Seite abgestimmter Schatten verschwindet auf einer schwarzen.

TokenWofürHellDunkel
--ran-shadow-elevatedFlächen im Fluss, die zusätzlich einen Rahmen haben: r-card, r-section0 1px 2px rgba(0,0,0,.04), 0 2px 4px -2px rgba(0,0,0,.05)0 1px 2px rgba(0,0,0,.16)
--ran-shadow-menuVorübergehende Ebenen über dem Inhalt: Auswahlliste, Select-Menü, Popover, Hinweis0 2px 4px rgba(0,0,0,.05), 0 8px 24px -6px rgba(0,0,0,.14)0 1px 1px rgba(0,0,0,.2), 0 4px 8px -4px rgba(0,0,0,.4), 0 16px 24px -8px rgba(0,0,0,.5)
--ran-shadow-modalBlockierende Dialoge: r-modal0 4px 12px rgba(0,0,0,.08), 0 20px 48px -12px rgba(0,0,0,.22)0 1px 1px rgba(0,0,0,.2), 0 8px 16px -4px rgba(0,0,0,.4), 0 24px 32px -8px rgba(0,0,0,.5)

Rahmenlose Overlays verlassen sich für die Abgrenzung allein auf den Schatten, die Overlay-Stufen tragen also echtes Gewicht; ein Overlay, das auf die gehobene Stufe zurückfällt, wirkt flach und an die Seite geheftet.

Stapelung

Schwebende Overlays werden nach <body> portalt und brauchen daher eine ausdrückliche Stufe:

TokenStandardWofür
--ran-z-modal1000Blockierende Dialoge und ihre Maske
--ran-z-dropdown1100Auswahlliste / Select-Menü / Popover: über dem Modal, damit ein Select in einem Dialog sichtbar bleibt
--ran-z-message1200Hinweise und Benachrichtigungen: immer obenauf

Die Leiter beginnt bei 1000, damit sie gewöhnliches Seitenrahmenwerk überragt (Navigationsleisten und Hintergründe liegen üblicherweise im Zehnerbereich). Überschreibe eine Stufe an :root oder je Komponente (--ran-dropdown-host-z-index, --ran-modal-root-z-index, --ran-message-z-index), niemals mit !important.

Bewegung

TokenWertVerwendung
--ran-motion-duration-fast0.15sÜbergänge beim Überfahren und Drücken
--ran-motion-duration-base0.2sPopovers, Menüs
--ran-motion-duration-slow0.35sGrößere Einblendungen
Beschleunigungs-TokenKurveCharakter
--ran-motion-ease-standardcubic-bezier(0.645,0.045,0.355,1)Ein und aus, für den allgemeinen Gebrauch
--ran-motion-ease-snappycubic-bezier(0.33,0,0.15,1)Schnell, ohne Überschwingen: Schalter
--ran-motion-ease-springcubic-bezier(0.34,1.26,0.5,1)Leichtes Überschwingen: Schaltflächen, Karten
--ran-motion-ease-bouncycubic-bezier(0.34,1.56,0.64,1)Verspieltes Überschwingen: Gefällt mir, In den Warenkorb
--ran-motion-ease-smoothcubic-bezier(0.4,0,0.2,1)Ruhig, ohne Überschwingen: Einblendungen, Layout

Die spring-Familie ist aus abgestimmten SwiftUI-Federn destilliert (Response und Dämpfung auf eine Bézier mit einem einzigen Überschwingen eingedampft).

Kombiniere sie nur mit Bewegungseigenschaften: transform, opacity, die Geometrie des Kastens. Paletteneigenschaften (background-color, color, border-color, box-shadow, fill, stroke) tragen bewusst keinen Standardübergang, denn CSS kann eine Interaktion nicht von einem Themenwechsel unterscheiden: Jede Überblendung, die du einer Farbe gibst, feuert auch beim Wechsel zwischen hell und dunkel. Jede Komponente bietet trotzdem einen --ran-*-transition-Haken, falls du es doch willst.

Fokus

TokenWertWofür
--ran-focus-ring0 0 0 2px var(--ran-background-100), 0 0 0 4px var(--ran-blue-700)Der Standardring, als box-shadow
--ran-focus-ring-inverse-color#fffDie Ringfarbe für eine Fläche, die in beiden Themes dunkel ist

Der Ring hat zwei Lagen: einen inneren in der Hintergrundfarbe und einen äußeren in Blau. So bleibt er auf jeder Fläche sichtbar und bleibt blau, statt dem inzwischen monochromen Primär zu folgen.

--ran-focus-ring-inverse-color wird bewusst nicht im Dunkelmodus neu definiert: Es gibt ihn für eine Komponente, deren eigene Fläche unabhängig vom Seitenthema fest dunkel ist (die Steuerleiste von r-player, über beliebigem Video), und diese Fläche ändert sich nicht, wenn die Seite es tut.

Skin-Primitive

Die wenigen strukturellen Werte, die Komponenten teilen und die weder Farbe noch Größe noch Schrift sind. Bewusst knapp gehalten: Diese Ebene war einmal viel größer, und das meiste davon fiel mit den Theme-Paketen weg.

TokenWertWofür
--ran-skin-border-width1pxDie Rahmenstärke, die Komponenten zeichnen
--ran-skin-border-stylesolidDer Rahmenstil, den Komponenten zeichnen
--ran-skin-border-image-width4pxDer Einzug von border-image-slice, geteilt von button/checkbox/input/modal/message
--ran-skin-raised-shadowvar(--ran-shadow-elevated)Der Schatten gehobener Flächen, indirekt, damit ein Skin ihn ändern kann
--ran-skin-font-familyvar(--ran-font-family)Die Schriftfamilie der Komponenten, auf dieselbe Weise indirekt

Was der Dunkelmodus neu definiert

data-ran-theme="dark" an <html> (oder an einem beliebigen Teilbaum, siehe Themengestaltung) definiert die Basispalette neu und sonst nichts, mit drei Ausnahmen, die sich nicht über eine Skala auflösen lassen:

  • die gesamte Ebene 1: jede Sprosse von Grau, Grau-Alpha, Blau, Rot, Bernstein und Grün sowie beide Hintergründe;
  • --ran-color-bg-elevated, das im Dunklen auf --ran-gray-100 zeigt, damit sich eine Karte von einer schwarzen Seite abhebt, statt darin zu verschwinden;
  • --ran-color-primary-hover / -active, die Literale sind statt Verweise auf eine Skala;
  • alle drei Schattenstufen, für einen dunklen Grund neu abgestimmt.

Alles andere (jedes weitere semantische Token, jede Größe, jede Dauer) ist genau einmal definiert.

Komponenten-Tokens

Unterhalb der semantischen Ebene stellt jede Komponente eigene Haken bereit, benannt als:

--ran-{component}-{element}[-{state}]-{property}

zum Beispiel --ran-btn-hover-background, --ran-select-search-active-border-width. Sie fallen standardmäßig auf semantische Tokens zurück: var(--ran-btn-background, var(--ran-color-primary, #171717)). Ein semantisches Token zu überschreiben erreicht sie also alle, ein Komponenten-Token zu überschreiben verengt die Änderung auf ein einzelnes Element.

Die vollständige erzeugte Liste ist style-tokens-public.md im Repository; die API je Element steht hier. Wie man sie anwendet, zeigt die Themengestaltung.

Tokens im eigenen CSS verwenden

css
.panel {
  background: var(--ran-color-bg-elevated);
  color: var(--ran-color-text);
  border: var(--ran-skin-border-width) var(--ran-skin-border-style) var(--ran-color-border);
  border-radius: var(--ran-radius-md);
  padding: var(--ran-space-4);
  box-shadow: var(--ran-shadow-elevated);
}

Drei Regeln halten das im Dunkeln sicher:

  1. Kein roher Hex-Wert für etwas, das dem Theme folgen soll.
  2. Ein Rückfallwert muss ein Token nennen, das mitwechselt: var(--ran-color-text, var(--ran-gray-1000)), niemals var(--ran-color-text, #171717).
  3. Ein Rückfallwert muss ein Token nennen, das es gibt, sonst fällt die Deklaration weg und das Element behält stillschweigend, was es geerbt hat.

Jedes globale Token, das die Bibliothek deklariert, steht auf dieser Seite, und ein Unit-Test schlägt fehl, wenn eines hinzukommt, ohne hier dokumentiert zu sein. Komponentenbezogene Tokens werden getrennt erzeugt, in style-tokens-public.md.

Veröffentlicht unter der MIT-Lizenz.