Browse Source
Adopts a build-free modern CSS stack per todo.md: six cascade layers (reset → tokens → base → layout → components → utilities), modern-normalize in the reset layer, hand-rolled design tokens with an Open Props cherry-pick workflow, and self-hosted Adelle (serif, headings) plus Open Sans (variable, body) fonts. Existing starterkit component CSS is wrapped in @layer components; the previously-misnamed `base` library is renamed to `components`; YAML config files and architectural CSS files gain explanatory headers; CLAUDE.md documents the full setup so future work can pick it up cold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>master
71 changed files with 1022 additions and 1 deletions
@ -0,0 +1,74 @@
|
||||
# CLAUDE.md |
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
||||
|
||||
## What this is |
||||
|
||||
`druid` is a Drupal 11 custom theme generated from core's `starterkit_theme` (generator: `starterkit_theme:11.3.7`). It declares `stable9` as its base theme in `druid.info.yml`, so any template not overridden here falls back to `stable9`'s version. The theme lives at `web/themes/custom/druid` inside a larger Drupal site (sibling theme `orion` lives next to it; site-level `composer.json` is at the repo root). |
||||
|
||||
There is no build pipeline, no test suite, no linter, and no preprocessor in this directory. CSS is hand-authored modern CSS (custom properties, cascade layers, nesting, container queries, `:where()`, `color-mix()`, etc.) and consumed directly by Drupal. "Building" the theme means clearing Drupal's cache so it picks up `.yml` and template changes — CSS *content* edits need no rebuild. See `todo.md` for the design rationale that informed this stack choice (no Sass, no PostCSS, no Pico). |
||||
|
||||
## CSS architecture: cascade layers |
||||
|
||||
This is the central architectural decision and everything below it depends on understanding it. |
||||
|
||||
**Layer order**, declared once in `css/layers.css` and authoritative for the whole theme: |
||||
|
||||
```css |
||||
@layer reset, tokens, base, layout, components, utilities; |
||||
``` |
||||
|
||||
Later layers win against earlier ones regardless of selector specificity. Each layer is filled by exactly one file (except `components`, which is split per UI element), and each file is registered as its own library so loading is deterministic: |
||||
|
||||
| Layer | File(s) | Library | SMACSS cat. | Weight | |
||||
| ------------ | -------------------------------- | ------------------ | ----------- | ------ | |
||||
| (order decl) | `css/layers.css` | `druid/layers` | `base` | -100 | |
||||
| reset | `css/lib/modern-normalize.css` | `druid/reset` | `base` | -90 | |
||||
| (font-face) | `css/fonts.css` | `druid/fonts` | `base` | -85 | |
||||
| tokens | `css/tokens.css` | `druid/tokens` | `base` | -80 | |
||||
| base | `css/base.css` | `druid/base` | `base` | -70 | |
||||
| layout | `css/layout.css` | `druid/layout` | `layout` | -60 | |
||||
| components | `css/components/*.css` (32 files)| `druid/components` | `component` | -10/file | |
||||
| utilities | `css/utilities.css` | `druid/utilities` | `theme` | -50 | |
||||
|
||||
All eight libraries are attached on every page via `druid.info.yml` `libraries:` in cascade order. **Do not remove or reorder that list** — the layer-order declaration in `layers.css` must parse before any `@layer foo { … }` block, or the order becomes whatever Drupal happens to load first. |
||||
|
||||
`css/fonts.css` is the one architecture file that intentionally lives *outside* any `@layer` block. `@font-face` declarations register font resources rather than cascade rules — they don't compete in the cascade, so layer membership is irrelevant. The font files themselves live in `fonts/<family>/` (e.g. `fonts/adelle/`, `fonts/open-sans/`). |
||||
|
||||
**Critical footgun: unlayered CSS beats all layered CSS.** Any rule outside a `@layer { … }` block silently wins against everything inside layers — this is per the CSS spec, not a bug. Every CSS file in this theme is wrapped; any new rule you author must go *inside* the appropriate `@layer NAME { … }` block, never alongside it. |
||||
|
||||
**`!important` inverts layer order.** An `!important` declaration in an *earlier* layer beats `!important` in a later layer. Avoid `!important` inside `@layer utilities` — the layer is already designed to win. |
||||
|
||||
### Where to add a new CSS rule |
||||
|
||||
| Kind of rule | Goes in | |
||||
| --------------------------------------------------------------------- | -------------------------------------------------- | |
||||
| A `--custom-property` definition (color, size, font, radius, etc.) | `css/tokens.css` | |
||||
| A new `@font-face` / web font | `css/fonts.css` (+ woff2 file in `fonts/<family>/`)| |
||||
| Bare element selector (`body`, `a`, `h1`, `ul`, …) | `css/base.css` | |
||||
| Region/grid/container arrangement (`.layout-container`, page shell) | `css/layout.css` | |
||||
| A discrete UI widget (button, card, menu, form control, Drupal block) | `css/components/<thing>.css` | |
||||
| Single-purpose helper (`.text-center`, `.mt-4`, print toggles) | `css/utilities.css` | |
||||
| A browser default override | `css/lib/modern-normalize.css`* | |
||||
|
||||
*Only when re-vendoring a new modern-normalize release — see the file header for the workflow. |
||||
|
||||
### Tokens / Open Props |
||||
|
||||
`css/tokens.css` holds CSS custom properties on `:root`. We do **not** import the full Open Props library — instead, we cherry-pick individual tokens by copying their declarations into this file. The workflow is documented in the file's header comment; re-read it before adding tokens. The short version: browse https://open-props.style, copy the `--prop: value;` line, paste into the relevant section, preserve the Open Props name. |
||||
|
||||
## Other layout that matters |
||||
|
||||
- `druid.info.yml` — declares base theme, always-on libraries (one per cascade layer), and `libraries-extend` entries that attach extra component CSS only when the corresponding core library is in use (e.g. `core/drupal.dialog` → `druid/dialog`). The `libraries-extend` libraries (`dialog`, `dropbutton`, `file`, `item-list`, `progress`, `drupal.tablesort`, `user`) are separate from the always-on `components` library because they target widgets that don't appear on every page. |
||||
- `druid.libraries.yml` — every CSS library. New component CSS files added to `css/components/` need a corresponding entry under the `components:` library (or their own library if they should be conditional via `libraries-extend`). |
||||
- `druid.theme` — `hook_preprocess_HOOK` functions. Currently only `druid_preprocess_image_widget()`, which strips the preview container when the user lacks access. |
||||
- `templates/` — Twig overrides organized by Drupal's own taxonomy (`block/`, `content/`, `content-edit/`, `dataset/`, `field/`, `form/`, `layout/`, `misc/`, `navigation/`, `user/`, `views/`). Filenames follow Drupal's theme-hook-suggestion convention (`field--node--title.html.twig`, `block--system-menu-block.html.twig`, etc.) — renaming them breaks the override. |
||||
|
||||
## Working in this theme |
||||
|
||||
- **Cache clear**: required after any `.yml`, `.theme`, or template change — `drush cr` from the site root. CSS *content* edits do not require it; only adding/removing files or library entries does. |
||||
- **New component CSS**: create the file in `css/components/`, wrap its rules in `@layer components { … }`, register it under the `components:` library in `druid.libraries.yml` with `weight: -10`. If the styles should only load when a specific core library is in use, give it its own library and wire it via `libraries-extend` in `druid.info.yml` instead. |
||||
- **New Twig override**: copy the upstream template from `core/themes/stable9/templates/…` (or the originating module) into the matching subdirectory here — don't write from scratch; the docblock comments document available variables and are worth keeping. |
||||
- **New preprocess hook**: follow the existing signature in `druid.theme` (`array &$variables`, `: void` return type, `/** Implements hook_X(). */` docblock). |
||||
- **Drupal core / contrib CSS is unlayered.** That means it wins against any `@layer components` rule you write — this is the one place the architecture leaks. Options: bump specificity for the override, or wrap the offending core CSS via a future `hook_css_alter` (not currently implemented). Don't reach for `!important` reflexively. |
||||
- The repo is generated from a starter kit; upstream Drupal docs at https://www.drupal.org/docs/core-modules-and-themes/core-themes/starterkit-theme are the canonical reference for regeneration and theme-API conventions. |
||||
@ -0,0 +1,29 @@
|
||||
/** |
||||
* @file |
||||
* Element-level defaults for the druid theme. |
||||
* |
||||
* Bare element selectors only (body, headings, links, lists, etc.). For |
||||
* discrete UI components, see css/components/. For page-level structure, |
||||
* see css/layout.css. Every rule lives in the `base` cascade layer so it |
||||
* loses cleanly to anything in layout / components / utilities. |
||||
*/ |
||||
@layer base { |
||||
body { |
||||
background: var(--surface-1); |
||||
color: var(--text-1); |
||||
font-family: var(--font-sans); |
||||
line-height: 1.5; |
||||
} |
||||
|
||||
/* Headings use the serif token (Adelle). Drupal core inserts <h1>–<h6> |
||||
in unpredictable places (admin toolbars, contextual links, system |
||||
blocks), so this targets bare heading elements — re-declare in a |
||||
component or utility if a specific UI surface needs the sans family. */ |
||||
h1, h2, h3, h4, h5, h6 { |
||||
font-family: var(--font-serif); |
||||
} |
||||
|
||||
a { |
||||
color: var(--brand); |
||||
} |
||||
} |
||||
@ -0,0 +1,120 @@
|
||||
/** |
||||
* @file |
||||
* @font-face declarations for the druid theme. |
||||
* |
||||
* Two families are registered: Adelle (serif, headings) and Open Sans |
||||
* (sans, body). All files are self-hosted under /fonts — no external |
||||
* requests at runtime. The token mapping (`--font-serif`, `--font-sans`) |
||||
* lives in css/tokens.css; rules that *apply* the fonts live in |
||||
* css/base.css. |
||||
* |
||||
* @font-face is intentionally NOT wrapped in a cascade layer. @font-face |
||||
* registers a font resource — it does not produce cascade rules that |
||||
* compete with anything, so layer membership is irrelevant. |
||||
* |
||||
* ───────────────────────────────────────────────────────────────────────── |
||||
* Adelle |
||||
* ───────────────────────────────────────────────────────────────────────── |
||||
* |
||||
* Self-hosted from /fonts/adelle/. Four weights/styles registered to keep |
||||
* the per-page font payload reasonable: Regular, Italic, Bold, BoldItalic. |
||||
* The /fonts/adelle/ folder ships additional weights (Light, Semibold, |
||||
* ExtraBold, Heavy, etc.) — to enable one, add a matching @font-face |
||||
* block below, pointing at the corresponding _.woff2 file. |
||||
* |
||||
* ───────────────────────────────────────────────────────────────────────── |
||||
* Open Sans |
||||
* ───────────────────────────────────────────────────────────────────────── |
||||
* |
||||
* Self-hosted from /fonts/open-sans/. Each .woff2 is a VARIABLE font — |
||||
* a single file covers the full weight range (300 to 800), so we declare |
||||
* the weight as a range and let the browser pick a weight per element. |
||||
* |
||||
* Two unicode-range subsets are registered per style: `latin` for English |
||||
* and standard western glyphs, `latin-ext` for accented characters, |
||||
* Esperanto, etc. The browser only downloads the subset it needs, based |
||||
* on the actual code points used on the page. |
||||
* |
||||
* To re-vendor (or add subsets like cyrillic/greek/vietnamese): |
||||
* 1. Visit https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap |
||||
* with a modern browser UA (curl -A "Mozilla/5.0 ... Chrome/..."). |
||||
* 2. Download the woff2 URLs Google returns for the subsets you want. |
||||
* 3. Save into /fonts/open-sans/ and add @font-face blocks below |
||||
* matching the unicode-range from Google's CSS. |
||||
*/ |
||||
|
||||
/* Adelle — Regular */ |
||||
@font-face { |
||||
font-family: 'Adelle'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
font-display: swap; |
||||
src: url('../fonts/adelle/Adelle_Reg.woff2') format('woff2'); |
||||
} |
||||
|
||||
/* Adelle — Italic */ |
||||
@font-face { |
||||
font-family: 'Adelle'; |
||||
font-style: italic; |
||||
font-weight: 400; |
||||
font-display: swap; |
||||
src: url('../fonts/adelle/Adelle_Italic.woff2') format('woff2'); |
||||
} |
||||
|
||||
/* Adelle — Bold */ |
||||
@font-face { |
||||
font-family: 'Adelle'; |
||||
font-style: normal; |
||||
font-weight: 700; |
||||
font-display: swap; |
||||
src: url('../fonts/adelle/Adelle_Bold.woff2') format('woff2'); |
||||
} |
||||
|
||||
/* Adelle — Bold Italic */ |
||||
@font-face { |
||||
font-family: 'Adelle'; |
||||
font-style: italic; |
||||
font-weight: 700; |
||||
font-display: swap; |
||||
src: url('../fonts/adelle/Adelle_BoldItalic.woff2') format('woff2'); |
||||
} |
||||
|
||||
/* Open Sans — Upright, latin */ |
||||
@font-face { |
||||
font-family: 'Open Sans'; |
||||
font-style: normal; |
||||
font-weight: 300 800; |
||||
font-display: swap; |
||||
src: url('../fonts/open-sans/OpenSans-Latin.woff2') format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
|
||||
/* Open Sans — Upright, latin-ext */ |
||||
@font-face { |
||||
font-family: 'Open Sans'; |
||||
font-style: normal; |
||||
font-weight: 300 800; |
||||
font-display: swap; |
||||
src: url('../fonts/open-sans/OpenSans-LatinExt.woff2') format('woff2'); |
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
|
||||
/* Open Sans — Italic, latin */ |
||||
@font-face { |
||||
font-family: 'Open Sans'; |
||||
font-style: italic; |
||||
font-weight: 300 800; |
||||
font-display: swap; |
||||
src: url('../fonts/open-sans/OpenSans-Italic-Latin.woff2') format('woff2'); |
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
||||
} |
||||
|
||||
/* Open Sans — Italic, latin-ext */ |
||||
@font-face { |
||||
font-family: 'Open Sans'; |
||||
font-style: italic; |
||||
font-weight: 300 800; |
||||
font-display: swap; |
||||
src: url('../fonts/open-sans/OpenSans-Italic-LatinExt.woff2') format('woff2'); |
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; |
||||
} |
||||
@ -0,0 +1,16 @@
|
||||
/** |
||||
* @file |
||||
* Cascade layer order for the druid theme. |
||||
* |
||||
* Earlier layers lose to later ones in the cascade, regardless of selector |
||||
* specificity. Unlayered CSS beats all layered CSS — if you author a new |
||||
* stylesheet for this theme, wrap it in the appropriate @layer block. |
||||
* |
||||
* - reset: modern-normalize and any browser-default resets. |
||||
* - tokens: :root custom property definitions (colors, spacing, type). |
||||
* - base: element-level defaults (body, headings, links, lists). |
||||
* - layout: page-level structural rules (regions, grids, containers). |
||||
* - components: discrete UI components and Drupal core overrides. |
||||
* - utilities: single-purpose helpers; intentionally wins last. |
||||
*/ |
||||
@layer reset, tokens, base, layout, components, utilities; |
||||
@ -0,0 +1,22 @@
|
||||
/** |
||||
* @file |
||||
* Page-level structural rules for the druid theme. |
||||
* |
||||
* This layer holds rules that arrange regions, not rules that style their |
||||
* contents — grids, flex containers, max-widths, page padding, sidebar |
||||
* positioning. If a rule could move from `<header>` to `<aside>` without |
||||
* changing meaning, it belongs here; if it's tied to a specific UI element |
||||
* (a card, a button, a menu item), put it in css/components/. |
||||
* |
||||
* Classes referenced by templates/layout/page.html.twig: |
||||
* .layout-container — outermost wrapper around the whole page |
||||
* .layout-content — main content column inside <main> |
||||
* .layout-sidebar-first — optional aside before content |
||||
* .layout-sidebar-second — optional aside after content |
||||
* |
||||
* Container queries (@container) are a natural fit for region-internal |
||||
* responsiveness — see todo.md for the rationale. |
||||
*/ |
||||
@layer layout { |
||||
|
||||
} |
||||
@ -0,0 +1,243 @@
|
||||
/* |
||||
* Vendored copy of modern-normalize for the druid theme. |
||||
* |
||||
* To re-vendor a new upstream release: |
||||
* 1. curl -fsSL https://raw.githubusercontent.com/sindresorhus/modern-normalize/main/modern-normalize.css -o /tmp/mn.css |
||||
* 2. Replace everything between `@layer reset {` and the trailing `}` |
||||
* below with the contents of /tmp/mn.css. |
||||
* 3. Update the version on the banner line just below this comment. |
||||
* 4. Commit. |
||||
* |
||||
* The `@layer reset { … }` wrapper is local to this theme; upstream ships |
||||
* the file unwrapped. Keeping the reset inside its own cascade layer means |
||||
* any rule in tokens / base / layout / components / utilities wins against |
||||
* it without needing higher specificity. |
||||
*/ |
||||
|
||||
/*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */ |
||||
|
||||
@layer reset { |
||||
|
||||
/* |
||||
Document |
||||
======== |
||||
*/ |
||||
|
||||
/** |
||||
Use a better box model (opinionated). |
||||
*/ |
||||
|
||||
*, |
||||
::before, |
||||
::after { |
||||
box-sizing: border-box; |
||||
} |
||||
|
||||
/** |
||||
1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) |
||||
2. Correct the line height in all browsers. |
||||
3. Prevent adjustments of font size after orientation changes in iOS. |
||||
4. Use a more readable tab size (opinionated). |
||||
*/ |
||||
|
||||
html { |
||||
font-family: |
||||
system-ui, |
||||
'Segoe UI', |
||||
Roboto, |
||||
Helvetica, |
||||
Arial, |
||||
sans-serif, |
||||
'Apple Color Emoji', |
||||
'Segoe UI Emoji'; /* 1 */ |
||||
line-height: 1.15; /* 2 */ |
||||
-webkit-text-size-adjust: 100%; /* 3 */ |
||||
tab-size: 4; /* 4 */ |
||||
} |
||||
|
||||
/* |
||||
Sections |
||||
======== |
||||
*/ |
||||
|
||||
/** |
||||
Remove the margin in all browsers. |
||||
*/ |
||||
|
||||
body { |
||||
margin: 0; |
||||
} |
||||
|
||||
/* |
||||
Text-level semantics |
||||
==================== |
||||
*/ |
||||
|
||||
/** |
||||
Add the correct font weight in Chrome and Safari. |
||||
*/ |
||||
|
||||
b, |
||||
strong { |
||||
font-weight: bolder; |
||||
} |
||||
|
||||
/** |
||||
1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3) |
||||
2. Correct the odd 'em' font sizing in all browsers. |
||||
*/ |
||||
|
||||
code, |
||||
kbd, |
||||
samp, |
||||
pre { |
||||
font-family: |
||||
ui-monospace, |
||||
SFMono-Regular, |
||||
Consolas, |
||||
'Liberation Mono', |
||||
Menlo, |
||||
monospace; /* 1 */ |
||||
font-size: 1em; /* 2 */ |
||||
} |
||||
|
||||
/** |
||||
Add the correct font size in all browsers. |
||||
*/ |
||||
|
||||
small { |
||||
font-size: 80%; |
||||
} |
||||
|
||||
/** |
||||
Prevent 'sub' and 'sup' elements from affecting the line height in all browsers. |
||||
*/ |
||||
|
||||
sub, |
||||
sup { |
||||
font-size: 75%; |
||||
line-height: 0; |
||||
position: relative; |
||||
vertical-align: baseline; |
||||
} |
||||
|
||||
sub { |
||||
bottom: -0.25em; |
||||
} |
||||
|
||||
sup { |
||||
top: -0.5em; |
||||
} |
||||
|
||||
/* |
||||
Tabular data |
||||
============ |
||||
*/ |
||||
|
||||
/** |
||||
Correct table border color inheritance in Chrome and Safari. (https://issues.chromium.org/issues/40615503, https://bugs.webkit.org/show_bug.cgi?id=195016) |
||||
*/ |
||||
|
||||
table { |
||||
border-color: currentcolor; |
||||
} |
||||
|
||||
/* |
||||
Forms |
||||
===== |
||||
*/ |
||||
|
||||
/** |
||||
1. Change the font styles in all browsers. |
||||
2. Remove the margin in Firefox and Safari. |
||||
*/ |
||||
|
||||
button, |
||||
input, |
||||
optgroup, |
||||
select, |
||||
textarea { |
||||
font-family: inherit; /* 1 */ |
||||
font-size: 100%; /* 1 */ |
||||
line-height: 1.15; /* 1 */ |
||||
margin: 0; /* 2 */ |
||||
} |
||||
|
||||
/** |
||||
Correct the inability to style clickable types in iOS and Safari. |
||||
*/ |
||||
|
||||
button, |
||||
[type='button'], |
||||
[type='reset'], |
||||
[type='submit'] { |
||||
-webkit-appearance: button; |
||||
} |
||||
|
||||
/** |
||||
Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers. |
||||
*/ |
||||
|
||||
legend { |
||||
padding: 0; |
||||
} |
||||
|
||||
/** |
||||
Add the correct vertical alignment in Chrome and Firefox. |
||||
*/ |
||||
|
||||
progress { |
||||
vertical-align: baseline; |
||||
} |
||||
|
||||
/** |
||||
Correct the cursor style of increment and decrement buttons in Safari. |
||||
*/ |
||||
|
||||
::-webkit-inner-spin-button, |
||||
::-webkit-outer-spin-button { |
||||
height: auto; |
||||
} |
||||
|
||||
/** |
||||
1. Correct the odd appearance in Chrome and Safari. |
||||
2. Correct the outline style in Safari. |
||||
*/ |
||||
|
||||
[type='search'] { |
||||
-webkit-appearance: textfield; /* 1 */ |
||||
outline-offset: -2px; /* 2 */ |
||||
} |
||||
|
||||
/** |
||||
Remove the inner padding in Chrome and Safari on macOS. |
||||
*/ |
||||
|
||||
::-webkit-search-decoration { |
||||
-webkit-appearance: none; |
||||
} |
||||
|
||||
/** |
||||
1. Correct the inability to style clickable types in iOS and Safari. |
||||
2. Change font properties to 'inherit' in Safari. |
||||
*/ |
||||
|
||||
::-webkit-file-upload-button { |
||||
-webkit-appearance: button; /* 1 */ |
||||
font: inherit; /* 2 */ |
||||
} |
||||
|
||||
/* |
||||
Interactive |
||||
=========== |
||||
*/ |
||||
|
||||
/* |
||||
Add the correct display in Chrome and Safari. |
||||
*/ |
||||
|
||||
summary { |
||||
display: list-item; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,69 @@
|
||||
/** |
||||
* @file |
||||
* Design tokens for the druid theme. |
||||
* |
||||
* Custom properties live in the `tokens` cascade layer so any later layer |
||||
* (base, layout, components, utilities) can reference them without |
||||
* specificity concerns. To override a token in a narrower scope, re-declare |
||||
* it on a more specific selector inside a later layer — e.g. inside |
||||
* `@layer components { .card { --surface-1: #fafafa; } }`. |
||||
* |
||||
* ───────────────────────────────────────────────────────────────────────── |
||||
* Adding tokens cherry-picked from Open Props |
||||
* ───────────────────────────────────────────────────────────────────────── |
||||
* |
||||
* Open Props (https://open-props.style) is a library of pre-made CSS custom |
||||
* properties. We do NOT import the full library — instead, copy just the |
||||
* individual props we use, here, by hand. |
||||
* |
||||
* Why cherry-pick rather than @import the whole bundle: |
||||
* - Keeps payload small (the full library is ~20KB of props we'd never use). |
||||
* - No build step / dependency to manage. |
||||
* - You can see, in one file, every token the theme actually relies on. |
||||
* |
||||
* Workflow: |
||||
* 1. Browse https://open-props.style and find the token you want |
||||
* (e.g. `--shadow-3`, `--ease-elastic-1`, `--gray-7`). |
||||
* 2. Copy its declaration line — both the name and the value — into the |
||||
* appropriate section below. Preserve the Open Props name so you can |
||||
* look it up again later; rename only when you have a clear reason. |
||||
* 3. If you copy a token that depends on another OP token (e.g. a shadow |
||||
* that references `--shadow-color`), copy that one too. |
||||
* 4. Save. No build step, no `drush cr` needed for token *value* changes; |
||||
* a cache rebuild is only needed when adding/removing files or library |
||||
* entries. |
||||
* |
||||
* Reference for the source values: |
||||
* - Live docs: https://open-props.style |
||||
* - Source CSS: https://github.com/argyleink/open-props/tree/main/src |
||||
*/ |
||||
@layer tokens { |
||||
:root { |
||||
/* Typography — families registered in css/fonts.css; applied in css/base.css. |
||||
--font-sans body text (Open Sans, self-hosted variable font, weights 300–800) |
||||
--font-serif headings (Adelle, self-hosted, Regular/Italic/Bold/BoldItalic) |
||||
System fallbacks ensure usable text before the @font-face files load. */ |
||||
--font-sans: 'Open Sans', system-ui, sans-serif; |
||||
--font-serif: 'Adelle', Georgia, 'Times New Roman', serif; |
||||
|
||||
/* Sizing scale — hand-picked, loosely follows Open Props `--size-N`. */ |
||||
--size-1: 0.25rem; |
||||
--size-2: 0.5rem; |
||||
--size-3: 1rem; |
||||
--size-4: 1.5rem; |
||||
|
||||
/* Border radius */ |
||||
--radius-2: 8px; |
||||
|
||||
/* Surfaces (backgrounds) */ |
||||
--surface-1: #ffffff; |
||||
--surface-2: #f5f5f5; |
||||
|
||||
/* Text colors */ |
||||
--text-1: #111; |
||||
--text-2: #555; |
||||
|
||||
/* Brand */ |
||||
--brand: hsl(210 90% 50%); |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@
|
||||
/** |
||||
* @file |
||||
* Single-purpose helper classes for the druid theme. |
||||
* |
||||
* The utilities layer is declared LAST in the cascade order, so any rule |
||||
* here wins over base / layout / components without specificity tricks. |
||||
* That's the whole point: a helper like .text-center exists to override |
||||
* whatever a component would otherwise apply, on demand. |
||||
* |
||||
* What belongs here: |
||||
* - Single-property, single-purpose helpers (.text-center, .mt-4, .flex) |
||||
* - Print-only / screen-only toggles (.print-only, .no-print) |
||||
* |
||||
* What does NOT belong here: |
||||
* - Multi-rule component styling — use css/components/. |
||||
* - Token definitions — use css/tokens.css. |
||||
* - .visually-hidden — Drupal core's system module already ships this; |
||||
* don't redefine it or you'll fight the core selector for no reason. |
||||
* |
||||
* Gotcha: !important inverts cascade-layer order. An !important rule in |
||||
* an *earlier* layer (e.g. components) beats an !important rule here. |
||||
* Generally avoid !important inside @layer utilities — the layer already |
||||
* wins on its own; reaching for !important means something else is wrong. |
||||
*/ |
||||
@layer utilities { |
||||
|
||||
} |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,210 @@
|
||||
# D11 theme set up |
||||
|
||||
What I would recommend for your stack |
||||
Core stack |
||||
Reset / normalize |
||||
|
||||
Use: |
||||
|
||||
Modern Normalize |
||||
|
||||
Very small and sensible. |
||||
|
||||
Design tokens / vars |
||||
|
||||
Use either: |
||||
|
||||
your own variables |
||||
or selectively import from Open Props |
||||
|
||||
Open Props |
||||
|
||||
You do not need the whole library. |
||||
|
||||
You can literally copy only the tokens you want. |
||||
|
||||
Example: |
||||
|
||||
:root { |
||||
--font-sans: system-ui, sans-serif; |
||||
|
||||
--size-1: 0.25rem; |
||||
--size-2: 0.5rem; |
||||
--size-3: 1rem; |
||||
--size-4: 1.5rem; |
||||
|
||||
--radius-2: 8px; |
||||
|
||||
--surface-1: #ffffff; |
||||
--surface-2: #f5f5f5; |
||||
|
||||
--text-1: #111; |
||||
--text-2: #555; |
||||
|
||||
--brand: hsl(210 90% 50%); |
||||
} |
||||
|
||||
That alone gets you surprisingly far. |
||||
|
||||
Native CSS nesting is now good enough |
||||
|
||||
You can now do: |
||||
|
||||
.card { |
||||
padding: var(--size-4); |
||||
|
||||
& h2 { |
||||
margin-block-start: 0; |
||||
} |
||||
|
||||
& a { |
||||
color: var(--brand); |
||||
} |
||||
} |
||||
|
||||
No Sass needed. |
||||
|
||||
Browser support is now solid. |
||||
|
||||
Cascade layers are a huge replacement for old Sass architecture |
||||
|
||||
This is one of the biggest modern CSS improvements. |
||||
|
||||
@layer reset, tokens, base, layout, components, utilities; |
||||
|
||||
Then: |
||||
|
||||
@layer base { |
||||
body { |
||||
font-family: var(--font-sans); |
||||
} |
||||
} |
||||
|
||||
This gives you predictable override behavior without specificity nightmares. |
||||
|
||||
Honestly better than many old Sass architectures. |
||||
|
||||
Container queries are transformative for Drupal theming |
||||
|
||||
Instead of: |
||||
|
||||
@media (min-width: 900px) |
||||
|
||||
you can do: |
||||
|
||||
@container (min-width: 40rem) |
||||
|
||||
This is fantastic for: |
||||
|
||||
paragraphs |
||||
blocks |
||||
views |
||||
reusable Drupal components |
||||
|
||||
because components respond to their container, not the viewport. |
||||
|
||||
That maps beautifully to Drupal’s block-based structure. |
||||
|
||||
Suggested file structure |
||||
|
||||
Something like: |
||||
|
||||
css/ |
||||
reset.css |
||||
tokens.css |
||||
base.css |
||||
layout.css |
||||
components/ |
||||
buttons.css |
||||
cards.css |
||||
forms.css |
||||
navigation.css |
||||
|
||||
Then load in your .libraries.yml. |
||||
|
||||
Very maintainable. |
||||
|
||||
I would honestly skip Pico entirely then |
||||
|
||||
Given what you just said, I think you may actually prefer: |
||||
|
||||
normalize/reset |
||||
your own tokens |
||||
semantic HTML |
||||
native CSS features |
||||
tiny component CSS |
||||
|
||||
instead of Pico. |
||||
|
||||
Because Pico is opinionated. |
||||
You may eventually find yourself undoing its typography/layout assumptions. |
||||
|
||||
Whereas your own token system: |
||||
|
||||
stays tiny |
||||
teaches future-you exactly how the site works |
||||
avoids framework drift |
||||
keeps Drupal Twig very clean |
||||
Modern CSS features worth embracing immediately |
||||
|
||||
These are absolutely worth using now: |
||||
|
||||
clamp() |
||||
font-size: clamp(1rem, 2vw, 1.5rem); |
||||
logical properties |
||||
margin-block: 1rem; |
||||
padding-inline: 2rem; |
||||
|
||||
Much cleaner than top/right/bottom/left. |
||||
|
||||
:where() |
||||
|
||||
Low-specificity selectors: |
||||
|
||||
:where(article, section, aside) h2 { |
||||
margin-top: 0; |
||||
} |
||||
|
||||
Excellent for maintainability. |
||||
|
||||
color-mix() |
||||
background: color-mix(in srgb, var(--brand), white 90%); |
||||
|
||||
Incredibly useful. |
||||
|
||||
Tooling I would still keep |
||||
|
||||
Even without preprocessing, I’d still consider: |
||||
|
||||
Vite only for: |
||||
live reload |
||||
JS bundling |
||||
asset handling |
||||
|
||||
NOT CSS transforms. |
||||
|
||||
Or skip even that initially. |
||||
|
||||
Modern Drupal theming can genuinely be: |
||||
|
||||
CSS files |
||||
ES modules |
||||
libraries.yml |
||||
done. |
||||
|
||||
And that’s refreshing compared to the old webpack/gulp era. |
||||
|
||||
This approach fits Drupal especially well |
||||
|
||||
Drupal’s strengths are: |
||||
|
||||
semantic HTML |
||||
server rendering |
||||
reusable structured content |
||||
progressive enhancement |
||||
|
||||
A lightweight native CSS architecture aligns with that extremely well. |
||||
|
||||
You avoid importing frontend-app complexity into a CMS that doesn’t necessarily benefit from it. |
||||
|
||||
────────────────────────────────────────────────────────────────── |
||||
Loading…
Reference in new issue