Understanding and working with Dialtone's CSS cascade layer architecture.
Dialtone uses CSS Cascade Layers (@layer) to organize all styles into a predictable hierarchy. For normal declarations, utilities override components. This reduces cross-layer specificity conflicts and makes it easy to integrate custom styles.
Dialtone defines four cascade layers in priority order:
@layer dialtone.reset, dialtone.base, dialtone.components, dialtone.utilities;
!important) declarations:dialtone.utilities - Highest priority (utility classes)dialtone.components - Component stylesdialtone.base - Base styles (tokens, fonts, themes)dialtone.reset - Lowest priority (resets)!important declarations, the order reverses:dialtone.reset !important - Highest prioritydialtone.base !importantdialtone.components !importantdialtone.utilities !important - Lowest prioritydialtone.reset: CSS resets, e.g. (normalize.css), element resets (h1, h2)dialtone.base: Design Tokens (CSS Variables), @font-face, themes, global html/body rulesdialtone.components: Component styles (buttons, inputs, modals), recipes, typography classesdialtone.utilities: Utility classes with !important, generated utilities, responsive utilitiesWithin the same layer, traditional CSS specificity applies:
@layer dialtone.utilities {
.d-p16 { prop: value !important; } /* specificity: 0-1-0 */
div.d-p16 { prop: value !important; } /* specificity: 0-1-1 - WINS */
}
Reference: Specificity Calculator
Across different layers, layer order wins (specificity is ignored):
@layer dialtone.components {
div.d-card.d-card--active { prop: value; } /* specificity: 0-3-1 */
}
@layer dialtone.utilities {
.d-fc-primary { prop: value !important; } /* specificity: 0-1-0 - WINS */
}
Dialtone utility classes are designed to override component styles:
<!-- Utility classes override component defaults -->
<button class="d-foo d-p-300 d-bgc-critical">
Custom Button
</button>
For app-specific styles, create your own layer after Dialtone's utilities:
/* In your app's CSS file */
@layer dialtone.reset, dialtone.base, dialtone.components, dialtone.utilities, app;
@layer app {
.my-custom-button {
prop: value;
}
}
Styles outside any @layer have the highest priority:
/* These override ALL layered styles (including Dialtone utilities) */
.my-critical-override {
color: red !important;
}
Create an app layer after utilities:
@layer dialtone.reset, dialtone.base, dialtone.components, dialtone.utilities, app.overrides;
@layer app.overrides {
.d-foo--custom {
prop: value;
}
}
If using third-party libraries, wrap them in a layer between components and utilities:
@layer dialtone.reset, dialtone.base, dialtone.components, third-party, dialtone.utilities;
@layer third-party {
@import 'some-library/styles.css';
}
This ensures:
Styles not in a layer have higher priority than all layered styles for normal declarations:
/* Layered (lower priority) */
@layer dialtone.utilities {
.d-fc-primary { color: blue !important; }
}
/* Unlayered (higher priority) */
.some-class { color: red; } /* WINS over layered utilities */
However, !important reverses this: layered !important beats unlayered !important.
dialtone.utilities for custom stylesdialtone.reseth1, h2, etc.)Files: lib/build/less/dialtone-reset.less, typography reset section in utilities/typography.less
dialtone.base@font-face declarationshtml/body rulesFiles: Token CSS (via dialtone-tokens.cjs), dialtone-globals.less, dialtone-transitions.less, themes/default.less
dialtone.componentsFiles: components/*.less (49 files), recipes/*.less (21 files), component sections in utility files
dialtone.utilities!important.lg:d-d-block, etc.)Files: utilities/*.less (10 files), generated by postcss/dialtone-generators.cjs and postcss-responsive-variations plugin
Wrap your component styles in @layer dialtone.components:
// lib/build/less/components/my-component.less
@layer dialtone.components {
.d-my-component {
property: value;
&__header {
property: value;
}
}
}
Import in dialtone.less:
@import 'components/my-component';
Wrap utility classes in @layer dialtone.utilities:
// lib/build/less/utilities/my-utilities.less
@layer dialtone.utilities {
.d-my-util { property: value !important; }
}
If you need to share styles between files or layers, extract parametric mixins outside @layer blocks:
// Define OUTSIDE @layer for cross-file access
._my-mixin() {
display: flex;
align-items: center;
}
@layer dialtone.components {
.d-component {
._my-mixin(); // ✅ Works
}
}
Why: LESS treats @layer as a scope boundary. Mixins inside one layer cannot be accessed from another layer or file.
Avoid adding styles outside @layer blocks:
// BAD - unlayered styles override everything
.d-my-class {
property: value;
}
Don't put utilities in the components layer:
@layer dialtone.components {
// BAD - utilities belong in dialtone.utilities
.d-mt16 { property: value !important; }
}
Don't call mixins from inside different @layer blocks:
@layer dialtone.components {
.d-foo() { /* ... */ }
}
@layer dialtone.utilities {
.d-bar {
.d-foo(); // ❌ BREAKS - mixin not accessible from `dialtone.components`
}
}
After adding or modifying layered styles:
lib/dist/dialtone.css for proper @layer wrappingpnpm run lint - catches layer violationsCSS Cascade Layers documentation last updated Friday, September 4, 2026
fix/popover-modal-zindex-scope