|
| 1 | +--- |
| 2 | +heading: Dialtone now uses CSS Cascade Layers |
| 3 | +author: Francis Rupert |
| 4 | +posted: '2026-2-23' |
| 5 | +excerpt: 'Dialtone now uses CSS Cascade Layers to organize all styles into a predictable hierarchy, improving specificity control and making overrides more predictable.' |
| 6 | +--- |
| 7 | + |
| 8 | +<BlogPost :author="$frontmatter.author" :posted="parse($frontmatter.posted, 'y-M-d', new Date())" :heading="$frontmatter.heading" :excerpt="$frontmatter.excerpt"> |
| 9 | + |
| 10 | +## TLDR |
| 11 | + |
| 12 | +Dialtone now uses CSS Cascade Layers (`@layer`) to organize all styles into a predictable hierarchy. This improves specificity control, makes overrides more predictable, and eliminates the need for complex selector specificity hacks. |
| 13 | + |
| 14 | +**For Consumers**: No breaking changes. Styles work exactly as before, just more reliably. |
| 15 | + |
| 16 | +**For Contributors**: All new styles must be wrapped in the appropriate `@layer` block. See the [CSS Cascade Layers Guide](../../../guides/css-layers/) for details. |
| 17 | + |
| 18 | +## What Changed |
| 19 | + |
| 20 | +All Dialtone CSS is now organized into four cascade layers: |
| 21 | + |
| 22 | +```css |
| 23 | +@layer dialtone.reset, dialtone.base, dialtone.components, dialtone.utilities; |
| 24 | +``` |
| 25 | + |
| 26 | +### Layer Hierarchy |
| 27 | + |
| 28 | +1. **`dialtone.reset`** - CSS resets (normalize.css, typography resets) |
| 29 | +2. **`dialtone.base`** - Design tokens, fonts, themes, global styles |
| 30 | +3. **`dialtone.components`** - Component styles (buttons, inputs, modals, etc.) |
| 31 | +4. **`dialtone.utilities`** - Utility classes (spacing, colors, layout) |
| 32 | + |
| 33 | +## Why This Matters |
| 34 | + |
| 35 | +### Before: Specificity Wars |
| 36 | + |
| 37 | +Previously, ensuring utilities could override components required: |
| 38 | + |
| 39 | +- Adding `!important` to every utility |
| 40 | +- Carefully managing selector specificity |
| 41 | +- Loading CSS files in the correct order |
| 42 | +- Hoping third-party CSS didn't break things |
| 43 | + |
| 44 | +### After: Predictable Cascade |
| 45 | + |
| 46 | +With cascade layers: |
| 47 | + |
| 48 | +- ✅ **Utilities always override components** - Layer order guarantees it |
| 49 | +- ✅ **No specificity hacks needed** - Layer priority beats specificity |
| 50 | +- ✅ **Clear organization** - Every style has a clear home |
| 51 | +- ✅ **Third-party CSS control** - Can layer external CSS between Dialtone layers |
| 52 | + |
| 53 | +## How Layers Work |
| 54 | + |
| 55 | +### Normal Cascade (Highest to Lowest Priority) |
| 56 | + |
| 57 | +1. **Unlayered styles** (highest) |
| 58 | +2. `dialtone.utilities` |
| 59 | +3. `dialtone.components` |
| 60 | +4. `dialtone.base` |
| 61 | +5. `dialtone.reset` (lowest) |
| 62 | + |
| 63 | +### With `!important` (Order Reverses!) |
| 64 | + |
| 65 | +1. **`dialtone.reset !important`** (highest) |
| 66 | +2. `dialtone.base !important` |
| 67 | +3. `dialtone.components !important` |
| 68 | +4. `dialtone.utilities !important` |
| 69 | +5. **Unlayered `!important`** (lowest) |
| 70 | + |
| 71 | +> **Why utilities use `!important`**: Dialtone utilities are in the last layer with `!important`, giving them the highest effective priority for overriding component styles while staying organized. |
| 72 | +
|
| 73 | +## For Consumers |
| 74 | + |
| 75 | +### No Breaking Changes |
| 76 | + |
| 77 | +Your existing code continues to work. Utility classes override components just as before, but now with guaranteed layer ordering. |
| 78 | + |
| 79 | +### Writing Overrides |
| 80 | + |
| 81 | +If you need to override Dialtone styles, create your own layer after utilities: |
| 82 | + |
| 83 | +```css |
| 84 | +@layer dialtone.reset, dialtone.base, dialtone.components, dialtone.utilities, app; |
| 85 | + |
| 86 | +@layer app { |
| 87 | + .my-custom-styles { |
| 88 | + /* Your overrides here */ |
| 89 | + } |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +See [Using CSS Layers with Dialtone](../../../guides/css-layers/) for detailed guidance. |
| 94 | + |
| 95 | +### Third-Party CSS |
| 96 | + |
| 97 | +If you're using third-party CSS that conflicts with Dialtone, wrap it in a layer: |
| 98 | + |
| 99 | +```css |
| 100 | +@layer dialtone.reset, dialtone.base, dialtone.components, third-party, dialtone.utilities; |
| 101 | + |
| 102 | +@layer third-party { |
| 103 | + @import 'some-library/styles.css'; |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +This ensures Dialtone utilities can still override third-party styles. |
| 108 | + |
| 109 | +## For Contributors |
| 110 | + |
| 111 | +### All Styles Must Be Layered |
| 112 | + |
| 113 | +When adding new styles, wrap them in the appropriate layer: |
| 114 | + |
| 115 | +**Components:** |
| 116 | + |
| 117 | +```less |
| 118 | +@layer dialtone.components { |
| 119 | + .d-my-component { |
| 120 | + /* styles */ |
| 121 | + } |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +**Utilities:** |
| 126 | + |
| 127 | +```less |
| 128 | +@layer dialtone.utilities { |
| 129 | + .d-my-util { property: value !important; } |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +### Cross-Layer Mixins |
| 134 | + |
| 135 | +To share styles between layers, extract parametric mixins **outside** `@layer` blocks: |
| 136 | + |
| 137 | +```less |
| 138 | +// Outside @layer for cross-file access |
| 139 | +._my-mixin() { |
| 140 | + display: flex; |
| 141 | + align-items: center; |
| 142 | +} |
| 143 | + |
| 144 | +@layer dialtone.components { |
| 145 | + .d-component { ._my-mixin(); } |
| 146 | +} |
| 147 | +``` |
| 148 | + |
| 149 | +### Validation |
| 150 | + |
| 151 | +The build pipeline now validates that all Dialtone classes are properly layered. Unlayered classes will fail CI. |
| 152 | + |
| 153 | +See the [CSS Layers Contributor Guide](../../../guides/css-layers/) for complete documentation. |
| 154 | + |
| 155 | +## Examples |
| 156 | + |
| 157 | +### Utility Classes Override Components |
| 158 | + |
| 159 | +```html |
| 160 | +<!-- Component default: blue background --> |
| 161 | +<button class="d-btn d-bgc-critical"> |
| 162 | + <!-- Utility wins: red background --> |
| 163 | +</button> |
| 164 | +``` |
| 165 | + |
| 166 | +### Responsive Utilities |
| 167 | + |
| 168 | +Responsive utilities are now in the same layer as base utilities, ensuring consistent behavior: |
| 169 | + |
| 170 | +```html |
| 171 | +<div class="d-d-none lg:d-d-block"> |
| 172 | + <!-- Hidden by default, visible on large screens --> |
| 173 | +</div> |
| 174 | +``` |
| 175 | + |
| 176 | +### App Overrides |
| 177 | + |
| 178 | +```css |
| 179 | +@layer app.overrides { |
| 180 | + .d-btn--custom { |
| 181 | + border-radius: 999px; |
| 182 | + box-shadow: 0 4px 12px rgba(0,0,0,0.15); |
| 183 | + } |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +## Browser Support |
| 188 | + |
| 189 | +CSS Cascade Layers are supported in all modern browsers: |
| 190 | + |
| 191 | +- Chrome 99+ |
| 192 | +- Firefox 97+ |
| 193 | +- Safari 15.4+ |
| 194 | +- Edge 99+ |
| 195 | + |
| 196 | +For older browsers, styles still apply (unlayered), maintaining visual consistency with potentially different cascade behavior. |
| 197 | + |
| 198 | +## Learn More |
| 199 | + |
| 200 | +- [CSS Cascade Layers in Dialtone](../../../guides/css-layers/) |
| 201 | +- [MDN: CSS Cascade Layers](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer) |
| 202 | +- [CSS Cascade Layers Explainer](https://css.oddbird.net/layers/explainer/) |
| 203 | + |
| 204 | +## Migration Path |
| 205 | + |
| 206 | +No migration required for consumers. Existing Dialtone usage continues to work identically. |
| 207 | + |
| 208 | +If you have custom CSS that conflicts with Dialtone, consider wrapping it in a layer as described in [Using CSS Layers with Dialtone](../../../guides/css-layers/). |
| 209 | + |
| 210 | +</BlogPost> |
| 211 | + |
| 212 | +<script setup> |
| 213 | +import BlogPost from '@baseComponents/BlogPost.vue'; |
| 214 | +import { parse } from 'date-fns'; |
| 215 | +</script> |
0 commit comments