Vererbungsverhalten und Registrierung mit @property im Detail
Eine ungeregistrierte Custom Property vererbt in CSS immer, unabhängig davon, ob ihr Name einen lokalen, komponentenspezifischen Wert suggeriert. Erst @property gibt einer Custom Property einen festen Typ, einen garantierten Initialwert und ein explizites Vererbungsverhalten, und schließt damit eine ganze Klasse von Bugs, die in großen Design-Systemen sonst schwer aufzuspüren sind.
Inhaltsverzeichnis
- 1. Wie Custom Properties standardmäßig vererben, ohne Registrierung
- 2. @property-Grundlagen: syntax, inherits und initial-value
- 3. inherits: false im Detail: komponentenlokale Werte kapseln
- 4. Typischer Bug: unerwartetes Durchsickern ungeregistrierter Properties
- 5. invalid-at-computed-value-time: der Fallback-Unterschied bei ungültigen Werten
- 6. Registrierungsstrategie für ein konsistentes Design-System
- 7. CSS.registerProperty(): dieselbe Registrierung zur Laufzeit per JavaScript
- 8. Performance-Effekt: registrierte Custom Properties und Animation
- 9. Browser-Support und Migration bestehender Custom Properties
- 10. Zusammenfassung
- 11. FAQ
1. Wie Custom Properties standardmäßig vererben, ohne Registrierung
Eine Custom Property, die einfach mit --farbe: blue; deklariert wird, verhält sich in der Cascade nicht wie eine normale CSS-Eigenschaft, sondern wie ein reiner Text-Token, der grundsätzlich vererbt, genau wie color es tut. Das gilt selbst dann, wenn der Name der Property einen komponentenlokalen Zweck suggeriert, etwa --card-spacing: Ohne explizite Registrierung sickert dieser Wert in jedes verschachtelte Kindelement, sofern dort nicht ein neuer Wert für dieselbe Property gesetzt wird.
Dieses Standardverhalten überrascht viele, die aus objektorientierten Sprachen kommen und erwarten, dass eine lokal auf einer Komponente definierte Variable automatisch auch lokal bleibt. In CSS gibt es diese Kapselung ohne @property nicht: Jede Custom Property ist von Natur aus global im Sinne der Cascade und vererbt an alle Nachfahren, bis sie irgendwo überschrieben oder mit unset zurückgesetzt wird.
2. @property-Grundlagen: syntax, inherits und initial-value
Die @property-Regel registriert eine Custom Property mit drei Pflichtangaben: syntax legt den erlaubten Werttyp fest, etwa '<color>' oder '<length>', inherits legt explizit fest, ob der Wert an Kindelemente weitergegeben wird, und initial-value definiert den Wert, den die Property annimmt, wenn sie an keiner Stelle in der Cascade gesetzt wurde. Alle drei Angaben sind Pflicht, fehlt eine, wird die gesamte @property-Regel als ungültig verworfen und die Property bleibt ungeregistriert.
Der Typ aus syntax ist mehr als Dokumentation: Der Browser validiert jeden zugewiesenen Wert gegen diesen Typ und verwirft ungültige Zuweisungen, statt sie wie bei einer ungeregistrierten Property klaglos als Text zu übernehmen. Eine registrierte --dot-gap mit syntax: '<length>' akzeptiert also kein auto oder einen Farbwert, was Tippfehler schon beim Parsen sichtbar macht statt erst bei einem unerklärlich kaputten Layout.
@property --brand-color {
syntax: '<color>';
inherits: true;
initial-value: #4a1d96;
}
@property --card-spacing {
syntax: '<length>';
inherits: false;
initial-value: 16px;
}
3. inherits: false im Detail: komponentenlokale Werte kapseln
Setzt man inherits: false, verhält sich die Custom Property in Bezug auf Vererbung wie eine gewöhnliche, nicht vererbende CSS-Eigenschaft, etwa margin: Ein Kindelement sieht ausschließlich den initial-value, nicht den Wert, der weiter oben in der Cascade auf einem Vorfahren gesetzt wurde, es sei denn, das Kind erbt ihn explizit über inherit als Wert oder die Property wird direkt auf dem Kind neu gesetzt.
Das ist genau das Verhalten, das man sich für echte Komponenten-interne Werte wünscht, etwa den internen Innenabstand einer Card-Komponente, der nicht versehentlich in eine tief verschachtelte Unterkomponente durchsickern soll, nur weil diese zufällig denselben Property-Namen für einen völlig anderen Zweck verwendet. inherits: false macht Custom Properties damit zu einem echten Werkzeug für Kapselung, das ohne @property in CSS schlicht nicht existiert.
.card {
--card-spacing: 16px;
padding: var(--card-spacing);
}
.card .nested-widget {
/* --card-spacing here is 16px again (the initial-value),
NOT the 16px set on .card -- because inherits: false */
padding: var(--card-spacing, 8px);
}
4. Typischer Bug: unerwartetes Durchsickern ungeregistrierter Properties
Der häufigste Bug in großen CSS-Codebasen entsteht, wenn ein Team eine Custom Property in einer tief verschachtelten Komponente für einen lokalen Zweck einführt, etwa --icon-size in einem Button, ohne zu registrieren. Weil die Property ohne @property immer vererbt, kann ein ganz anderer, weiter oben in der Baumstruktur liegender Elternteil, der zufällig denselben Namen für etwas völlig anderes nutzt, plötzlich einen unerwarteten Wert an diesen Button weitergeben.
Solche Bugs sind besonders schwer zu debuggen, weil in den DevTools der "computed"-Wert korrekt anzeigt, woher die Vererbung kommt, aber der Entwickler oft gar nicht auf die Idee kommt, dort nachzuschauen, weil er die Property für rein lokal hält. Die zuverlässigste Prävention ist, jede Custom Property, die nicht bewusst global vererben soll, von Anfang an mit @property und inherits: false zu registrieren, statt sich auf Namenskonventionen wie ein vorangestelltes --_ zu verlassen.
5. invalid-at-computed-value-time: der Fallback-Unterschied bei ungültigen Werten
Ohne Registrierung gilt für Custom Properties das Konzept des "guaranteed-invalid value": Wird einer ungeregistrierten Property ein syntaktisch ungültiger Wert zugewiesen, verhält sie sich beim Verbrauch über var() so, als wäre sie gar nicht gesetzt, und der Fallback-Wert von var(--x, fallback) greift. Bei einer registrierten Property mit @property ist das Verhalten anders und für viele überraschend: Ein ungültiger Wert lässt die Property nicht auf den var()-Fallback zurückfallen, sondern auf den in @property definierten initial-value.
Dieser Unterschied wird zur Falle, wenn ein Team gewohnheitsmäßig einen var()-Fallback als Sicherheitsnetz einbaut, aber nicht merkt, dass dieser Fallback bei einer registrierten Property im Fehlerfall gar nicht mehr greift. Wer @property nutzt, muss den initial-value deshalb bewusst als den echten, einzigen Fallback-Mechanismus behandeln und den var()-Fallback nur noch als zusätzliche Doku-Absicherung verstehen, nicht als funktionale Garantie.
@property --gap {
syntax: '<length>';
inherits: false;
initial-value: 8px;
}
.box {
--gap: not-a-length; /* invalid for the registered <length> syntax */
/* Falls back to the initial-value (8px), NOT to the var() fallback below */
gap: var(--gap, 24px);
}
6. Registrierungsstrategie für ein konsistentes Design-System
In einem wachsenden Design-System lohnt es sich, alle Custom Properties, die als Design-Tokens dienen, in einer einzigen zentralen Stylesheet-Datei mit @property-Regeln zu registrieren, statt die Registrierung über verschiedene Komponenten-Dateien zu verstreuen. Diese zentrale Registrierung macht auf einen Blick sichtbar, welche Tokens im System existieren, welchen Typ sie haben und ob sie global vererben sollen oder komponentenlokal bleiben.
Als Faustregel für die Registrierungsstrategie gilt: Farb- und Typografie-Tokens, die bewusst durch den gesamten Baum durchgereicht werden sollen, bekommen inherits: true, während komponenteninterne Layout-Werte wie interne Abstände oder Icon-Größen konsequent mit inherits: false registriert werden, damit sie niemals versehentlich in fremde Komponenten hineinwirken.
7. CSS.registerProperty(): dieselbe Registrierung zur Laufzeit per JavaScript
Neben der deklarativen @property-Regel im Stylesheet gibt es mit CSS.registerProperty() eine gleichwertige JavaScript-API, die dieselben drei Angaben (Name, syntax, inherits, initialValue) zur Laufzeit setzt. Das ist nützlich, wenn ein Design-System seine Tokens dynamisch aus einer Konfigurationsdatei oder einem Theme-Objekt generiert und die Registrierung deshalb nicht als statisches CSS vorliegen kann, sondern erst zur Laufzeit feststeht.
Ein wichtiger Unterschied zur @property-Regel ist, dass CSS.registerProperty() bei einem Aufruf mit einem bereits registrierten Namen eine Exception wirft, während zwei widersprüchliche @property-Regeln im Stylesheet einfach nach den normalen Cascade-Regeln aufgelöst werden. Wer Tokens sowohl per JavaScript als auch per Stylesheet registriert, sollte deshalb klar festlegen, welcher der beiden Wege für welche Property die einzige Quelle der Wahrheit ist, um Laufzeitfehler durch doppelte Registrierung zu vermeiden.
8. Performance-Effekt: registrierte Custom Properties und Animation
Registrierte Custom Properties bringen neben der Typsicherheit einen praktischen Zusatznutzen: Weil der Browser dank syntax weiß, dass etwa --dot-gap eine Länge ist, kann er zwischen zwei Werten diskret animieren, ein Effekt, der bei ungeregistrierten Custom Properties nicht funktioniert, weil der Browser dort nur Text sieht und Text sich nicht interpolieren lässt. Eine transition auf einer ungeregistrierten Custom Property springt deshalb abrupt vom alten zum neuen Wert, statt sanft überzublenden.
Zusätzlich kann die Style-Engine bei registrierten Properties frühzeitig entscheiden, ob eine Änderung überhaupt eine Neuberechnung nachgelagerter Werte auslösen muss, weil Typ und Vererbungsverhalten schon zur Registrierungszeit feststehen, statt bei jeder Verwendung neu aus dem Kontext geraten zu werden. In sehr großen Stylesheets mit vielen Custom-Property-Referenzen kann sich das in der Style-Recalculation-Zeit messbar bemerkbar machen.
9. Browser-Support und Migration bestehender Custom Properties
@property wird mittlerweile von Chrome, Edge, Firefox und Safari unterstützt, sodass eine schrittweise Migration bestehender, ungeregistrierter Custom Properties in produktiven Projekten heute risikoarm möglich ist. Wichtig ist, dass eine @property-Regel additiv wirkt: Bestehende var()-Aufrufe funktionieren unverändert weiter, nur das beschriebene Fallback-Verhalten bei ungültigen Werten ändert sich, sobald die Registrierung greift.
Für die Migration empfiehlt sich ein schrittweises Vorgehen: zuerst die Custom Properties registrieren, die am häufigsten animiert werden oder in tief verschachtelten Komponenten für Verwirrung gesorgt haben, und erst danach die verbleibenden, unkritischen Tokens nachziehen. So lässt sich der Effekt jeder einzelnen Registrierung isoliert beobachten, statt alle Properties auf einmal umzustellen und bei einem Regressions-Bug nicht mehr zu wissen, welche Änderung ihn ausgelöst hat.
| Verhalten | Ungeregistrierte Property | @property mit inherits: true | @property mit inherits: false |
|---|---|---|---|
| Vererbung an Kinder | Immer, wie ein Text-Token | Ja, wie eine normale vererbende Eigenschaft | Nein, wie margin oder padding |
| Ungültiger Wert | Fällt auf var()-Fallback zurück | Fällt auf initial-value zurück | Fällt auf initial-value zurück |
| Animierbar | Nein, nur Text-Interpolation | Ja, typisierte Interpolation | Ja, typisierte Interpolation |
| Typprüfung beim Setzen | Keine | Ja, gegen syntax geprüft | Ja, gegen syntax geprüft |
Mironsoft
Modernes CSS, Layout-Architektur und Rendering-Performance
CSS, das wartbar bleibt statt mit jeder Änderung zu brechen?
Wir prüfen bestehende Stylesheets auf Spezifitäts-Chaos und Layout-Thrashing und bauen daraus eine CSS-Architektur mit Cascade Layers, Custom Properties und modernen Layout-Primitiven, die auch nach dem zehnten Feature noch verständlich ist.
CSS-Audit
Spezifität, Cascade-Konflikte und ungenutzte Selektoren systematisch aufdecken.
Architektur-Refactoring
Cascade Layers, Custom Properties und Design Tokens sauber einführen.
Performance-Tuning
Layout-Thrashing, teure Selektoren und Rendering-Engpässe gezielt beheben.
10. Zusammenfassung
Custom Properties und @property: Das Wichtigste auf einen Blick
Standardverhalten
Ungeregistrierte Custom Properties vererben immer, unabhängig vom Namen oder vom suggerierten Geltungsbereich.
Kapselung
@property mit inherits: false macht eine Custom Property zu einem echten, nicht vererbenden Wert wie margin.
Fallback-Falle
Bei registrierten Properties greift bei ungültigen Werten der initial-value, nicht der var()-Fallback.
Strategie
Design-Tokens zentral mit @property registrieren, globale Tokens mit inherits: true, komponentenlokale mit inherits: false.