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:

Seite Beantwortet
Designsystem (diese Seite) Was die Tokens sind: das Vokabular
Gestaltungsleitlinien Wie man wählt, wenn man eine Oberfläche baut
Themengestaltung Wie 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:

Sprosse Rolle Sprosse Rolle
100 Standardhintergrund 600 Rahmen im gedrückten Zustand
200 Hintergrund beim Überfahren 700 Deckende Füllung (Schaltfläche/Abzeichen)
300 Hintergrund beim Drücken 800 Deckende Füllung (Überfahren)
400 Standardrahmen 900 Sekundärer Text und Symbole
500 Rahmen beim Überfahren 1000 Primärer Text und Symbole

Hintergründe

Token Hell Dunkel Wofür
--ran-background-100 #ffffff #000000 Seitenhintergrund
--ran-background-200 #fafafa #000000 Dezente Zonen der Seite

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

Die Skala hinter Text, Rahmen und Flächen.

Sprosse Hell Dunkel
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.

Sprosse Hell Dunkel
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.

Sprosse Hell Dunkel
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.

Sprosse Hell Dunkel
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.

Sprosse Hell Dunkel
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.

Sprosse Hell Dunkel
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.

Token Löst auf zu Rolle
--ran-color-bg --ran-background-100 Seitenhintergrund
--ran-color-bg-subtle --ran-background-200 Dezente Zonen der Seite
--ran-color-bg-elevated --ran-background-100 · gray-100 (dunkel) Karten, Flächen
--ran-color-bg-muted --ran-gray-100 Eingesenkte / gedämpfte Füllungen
--ran-color-bg-hover --ran-gray-200 Fläche beim Überfahren
--ran-color-bg-active --ran-gray-300 Fläche beim Drücken
--ran-color-text --ran-gray-1000 Haupttext
--ran-color-text-secondary --ran-gray-900 Sekundärer Text
--ran-color-text-disabled --ran-gray-700 Deaktivierter Text
--ran-color-border --ran-gray-400 Standardrahmen
--ran-color-border-secondary --ran-gray-300 Dezenterer Rahmen
--ran-color-border-hover --ran-gray-500 Rahmen beim Überfahren
--ran-color-border-active --ran-gray-600 Rahmen beim Drücken
--ran-color-primary --ran-gray-1000 Die 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-100 Die Tinte auf einer primären Fläche
--ran-color-success --ran-green-700 Erfolg
--ran-color-warning --ran-amber-700 Warnung
--ran-color-danger --ran-red-700 Gefahr / Fehler
--ran-color-link --ran-blue-700 Links

--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:

Token Wert Token Wert
--ran-space-1 4px --ran-space-8 32px
--ran-space-2 8px --ran-space-10 40px
--ran-space-3 12px --ran-space-16 64px
--ran-space-4 16px --ran-space-24 96px
--ran-space-6 24px

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.

Token Wert Typischerweise
--ran-size-1 16px Kästchen einer Checkbox, kleines Symbol im Fließtext
--ran-size-2 18px
--ran-size-3 20px Symbol innerhalb eines Bedienelements
--ran-size-4 24px Symbolschaltfläche in einer Werkzeugleiste
--ran-size-5 28px Höhe eines kompakten Bedienelements
--ran-size-6 30px
--ran-size-7 32px Standardhö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

Token Wert
--ran-font-family Geist / Geist Sans, danach der System-UI-Stapel
--ran-font-mono Geist Mono, danach ui-monospace, SF Mono, Menlo, Consolas, …
--ran-font-size 14px (die Basisgröße)
--ran-line-height 1.5715

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

Rolle Verwendung Stärke-Token Größen-Tokens
heading Überschriften --ran-text-heading-weight (600) --ran-text-heading-1..4 (32/24/20/16px)
label Einzeilig, zum Überfliegen --ran-text-label-weight (500) --ran-text-label-1..3 (14/13/12px)
copy Mehrzeiliger Fließtext --ran-text-copy-weight (400) --ran-text-copy-1..2 (16/14px)
button Text auf Schaltflächen --ran-text-button-weight (500) --ran-text-button-size (14px)
mono Code, 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:

Token Wert Warum
--ran-text-heading-tracking -0.03em Überschriften brauchen in großen Graden eine engere Laufweite.
--ran-text-button-line-height 1 Sauberes 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:

import 'ranui/fonts'; // Bundler
<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

Token Wert Wofür
--ran-radius-sm 6px Bedienelemente: Schaltfläche, Eingabefeld, Auswahl
--ran-radius-md 12px Karten, Dialoge
--ran-radius-lg 16px Große Flächen
--ran-radius-full 9999px Pillenformen, 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.

Token Wofür Hell Dunkel
--ran-shadow-elevated Flächen im Fluss, die zusätzlich einen Rahmen haben: r-card, r-section 0 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-menu Vorübergehende Ebenen über dem Inhalt: Auswahlliste, Select-Menü, Popover, Hinweis 0 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-modal Blockierende Dialoge: r-modal 0 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:

Token Standard Wofür
--ran-z-modal 1000 Blockierende Dialoge und ihre Maske
--ran-z-dropdown 1100 Auswahlliste / Select-Menü / Popover: über dem Modal, damit ein Select in einem Dialog sichtbar bleibt
--ran-z-message 1200 Hinweise 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

Token Wert Verwendung
--ran-motion-duration-fast 0.15s Übergänge beim Überfahren und Drücken
--ran-motion-duration-base 0.2s Popovers, Menüs
--ran-motion-duration-slow 0.35s Größere Einblendungen
Beschleunigungs-Token Kurve Charakter
--ran-motion-ease-standard cubic-bezier(0.645,0.045,0.355,1) Ein und aus, für den allgemeinen Gebrauch
--ran-motion-ease-snappy cubic-bezier(0.33,0,0.15,1) Schnell, ohne Überschwingen: Schalter
--ran-motion-ease-spring cubic-bezier(0.34,1.26,0.5,1) Leichtes Überschwingen: Schaltflächen, Karten
--ran-motion-ease-bouncy cubic-bezier(0.34,1.56,0.64,1) Verspieltes Überschwingen: Gefällt mir, In den Warenkorb
--ran-motion-ease-smooth cubic-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

Token Wert Wofür
--ran-focus-ring 0 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 #fff Die 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.

Token Wert Wofür
--ran-skin-border-width 1px Die Rahmenstärke, die Komponenten zeichnen
--ran-skin-border-style solid Der Rahmenstil, den Komponenten zeichnen
--ran-skin-border-image-width 4px Der Einzug von border-image-slice, geteilt von button/checkbox/input/modal/message
--ran-skin-raised-shadow var(--ran-shadow-elevated) Der Schatten gehobener Flächen, indirekt, damit ein Skin ihn ändern kann
--ran-skin-font-family var(--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

.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.