Sub-theming (Starterkit)
When to Use
Create a sub-theme when you need to customize the base theme's components, add new components, override UI Styles, or set up a proper Tailwind/DaisyUI build pipeline. Alpha6 ships a full starterkit with Vite, Tailwind CSS 4, DaisyUI 5, and example component overrides.
Steps
Generate a sub-theme using Drupal's starterkit command:
# From the Drupal root:
php core/scripts/drupal generate-theme my_theme \
--starterkit ui_suite_daisyui_starterkit \
--path themes/custom
# Install npm dependencies:
cd themes/custom/my_theme
npm install
# Build assets:
npm run build
# Or watch for changes during development:
npm run dev
Then enable the theme:
drush theme:enable my_theme
drush config:set system.theme default my_theme
Starterkit Structure
my_theme/
my_theme.info.yml # Base theme: ui_suite_daisyui, library overrides
my_theme.libraries.yml # Library: dist/css/app.css + theme overrides
my_theme.theme # Preprocess hooks
my_theme.ui_styles.yml # Custom UI Styles (replaces some base theme styles)
my_theme.ui_skins.themes.yml # Theme switching config
my_theme.icons.yml # Icon pack config
package.json # npm deps: Tailwind 4, DaisyUI 5, Vite 6
vite.config.js # Rollup entry gen + viteStaticCopy + SVG sprite
postcss.config.js # PostCSS: stylelint + postcss-import + @tailwindcss/postcss
stylelint.config.js # Stylelint config for CSS linting
css/
app.pcss.css # Main entry: imports config, plugins, base, utilities
tailwind.config.pcss # @theme: fonts + custom grid column tokens
safelist.txt # Safelist of DaisyUI classes for the build
plugins/
daisyui.config.pcss # @plugin "daisyui": theme list, exclude: reset
tailwindcss.typography.config.pcss # @plugin "@tailwindcss/typography"
base/
base.pcss # @layer base: heading sizes (h1-h6)
themes/
dark.pcss.css # Theme override example (dark theme primary color)
utilities/
containers.pcss # container-xs through container-full
gap.pcss # Responsive gap-xs through gap-2xl
grid.pcss # grid-1 through grid-4, ratio variants
margin.pcss # Semantic mb-/mt- (xs through 2xl + auto)
padding.pcss # Semantic pb-/pt- (xs through 2xl + auto)
typography.pcss # title-2xl through title-2xs, copy-2xl through copy-xs
utilities.pcss # Custom utility overrides (empty by default)
components/
card/ # Overrides ui_suite_daisyui:card (replaces key)
cta/ # New component (CTA section)
section/ # New component (flexible layout section)
config/
optional/ # 10 block placement configs
install/ # Theme settings
schema/ # Config schema
templates/
system/ # System template overrides (page, block, etc.)
ui_patterns_library/ # UI Patterns library page templates
ui_styles_library/ # UI Styles overview template
ui_examples/ # UI Examples overview template
icons/
default/ # SVG icons (close, play) for sprite generation
dist/ # Build output (generated by Vite)
Vite Build Pipeline
The vite.config.js generates Rollup entry points from glob patterns:
// Entry point generation:
const cssGlob = {
'': 'css/**/*.pcss.css', // Global CSS -> dist/css/
'css': 'components/**/*.pcss.css', // Component CSS -> dist/css/components/
};
Key behaviors:
- Entry generation: Globs all
.pcss.cssfiles and creates Rollup entries - Component CSS copy:
viteStaticCopycopiesdist/css/components/tocomponents/so each SDC can reference its own compiled CSS - SVG sprite:
@pivanov/vite-plugin-svg-spritegeneratesdefault-icons.svgfromicons/default/ - PostCSS pipeline:
postcss-import+@tailwindcss/postcss(Tailwind 4) +stylelint - Minification: Only in production (
NODE_ENV=production)
npm scripts:
| Script | Command | Purpose |
|---|---|---|
npm run build |
NODE_ENV=production vite build |
Production build (minified) |
npm run dev |
NODE_ENV=development vite build --watch |
Development watch mode |
npm run lint:css |
stylelint '**/*.(pcss.css\|pcss)' |
Lint CSS files |
npm run lint:css:fix |
stylelint ... --fix |
Auto-fix lint issues |
Tailwind & DaisyUI Config
The starterkit uses Tailwind CSS 4's CSS-first configuration (no tailwind.config.js file). All config lives in .pcss files:
css/app.pcss.css -- Main entry:
@import "./tailwind.config.pcss";
@import "tailwindcss" source(none);
/* Plugins */
@import "./plugins/daisyui.config.pcss";
@import "./plugins/tailwindcss.typography.config.pcss";
/* Base styles and utilities */
@import "./base/base.pcss";
@import "./utilities/containers.pcss";
/* ... other utility imports ... */
/* Source directives: tell Tailwind where to scan for class usage */
@source "../templates/**/*.{twig,js}";
@source "../components/**/*.{twig,js,yml}";
@source "../*.ui_styles.yml";
@source "../../../contrib/ui_suite_daisyui/components/**/*.{twig,js,yml}";
@source "../../../contrib/ui_suite_daisyui/*.ui_styles.yml";
css/plugins/daisyui.config.pcss -- DaisyUI plugin:
@plugin "daisyui" {
exclude: reset; /* Prevent DaisyUI reset from conflicting with Tailwind preflight */
themes: light --default, dark, cupcake, aqua, /* ... all 35 themes ... */;
logs: false;
}
css/tailwind.config.pcss -- Theme tokens:
@theme {
--font-sans: ui-sans-serif, system-ui, sans-serif, ...;
--font-serif: ui-serif, Georgia, ...;
--font-mono: ui-monospace, SFMono-Regular, ...;
/* Custom grid column ratios */
--grid-cols--66x33: minmax(0, 2fr) minmax(0, 1fr);
--grid-cols--33x66: minmax(0, 1fr) minmax(0, 2fr);
--grid-cols--50x25x25: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr);
--grid-cols--25x50x25: minmax(0, 1fr) minmax(0, 2fr) minmax(0, 1fr);
--grid-cols--25x25x50: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 2fr);
}
css/themes/dark.pcss.css -- Theme override example:
@plugin "daisyui/theme" {
name: "dark";
--color-primary: oklch(55% 0.3 240);
}
Custom Utilities
The css/utilities/ directory defines semantic utility classes using Tailwind's @utility directive with responsive breakpoints. These replace raw Tailwind spacing/sizing values with a semantic token system:
| File | Utilities | Pattern |
|---|---|---|
containers.pcss |
container-xs through container-full |
Max-width + responsive padding |
gap.pcss |
gap-xs through gap-2xl |
Responsive gap (e.g., gap-sm = gap-2 md:gap-4) |
grid.pcss |
grid-1 through grid-4, ratio variants |
Responsive column layouts (e.g., grid-2-66x33) |
margin.pcss |
mb-xs/mt-xs through mb-2xl/mt-2xl + auto |
Responsive vertical margin |
padding.pcss |
pb-xs/pt-xs through pb-2xl/pt-2xl + auto |
Responsive vertical padding |
typography.pcss |
title-2xl through title-2xs, copy-2xl through copy-xs |
Custom font-size tokens via @theme variables |
Example -- gap-md resolves to gap-4 md:gap-6 (16px on mobile, 24px on md+).
Component Overrides
The starterkit includes three example components showing both override and creation patterns:
Card override (components/card/card.component.yml):
name: Card
replaces: 'ui_suite_daisyui:card'
# Full schema defined -- adds url prop and actions_position prop
# Twig template customizes card layout (link wrapper, action positioning)
The replaces key tells Drupal: "When anything references ui_suite_daisyui:card, use this sub-theme's card instead." The component defines its own full schema and Twig template. The card also includes card.pcss.css for custom styles (e.g., .card-title { @apply title-md; }).
CTA section (components/cta/) -- New component (no replaces):
name: CTA
group: CTA Section
variants: default, primary, secondary
slots: title, text, buttons
props: heading_level (1-6), centered (boolean)
Uses custom CSS classes (.cta, .cta-primary, .cta-secondary) defined in cta.pcss.css with Tailwind @apply directives. Demonstrates how to create entirely new components in the sub-theme.
Section layout (components/section/) -- New flexible layout component (experimental):
name: Section
group: Layout system
status: experimental
props: layout_attributes, grid_attributes, title_attributes, content_attributes,
footer_attributes (all $ref: ui-patterns://attributes), heading_level
slots: title, content, items (grid), footer
Designed to work with UI Styles -- each *_attributes prop accepts CSS classes from the admin UI. The Twig template applies sensible defaults (e.g., container-lg, gap-md, pt-auto/pb-auto) when no styles are set.
UI Styles Customization
The starterkit's .ui_styles.yml does two things:
- Adds custom style options using semantic tokens from the utility classes:
typography_font_size_starterkit:
category: "Typography"
label: "Font size"
options:
title-2xl: Title 2xl (42px)
title-xl: Title xl (36px)
# ... through title-2xs and copy-2xl through copy-xs
layout_grid_starterkit:
category: "Layout"
label: "Grid"
options:
grid-1: 1 column
grid-2: 2 columns 50x50
grid-2-33x66: 2 columns 33x66
grid-2-66x33: 2 columns 66x33
# ... through grid-4
layout_container_starterkit:
category: "Layout"
label: "Container"
options:
container-xs: Container xs (672px)
container-sm: Container sm (896px)
# ... through container-full
- Disables base theme styles that the starterkit replaces:
# Disable base theme styles replaced by starterkit versions
daisyui_glass:
enabled: false
typography_font_size:
enabled: false
spacing_padding_top:
enabled: false
spacing_padding_bottom:
enabled: false
spacing_margin_bottom:
enabled: false
effects_box_shadow:
enabled: false
sizing_width:
enabled: false
daisyui_mask:
enabled: false
Library Override Pattern
The starterkit's .info.yml uses libraries-override to disable the base theme's compiled CSS while using its own:
libraries:
- my_theme/daisyui # Sub-theme's own library
libraries-override:
ui_suite_daisyui/daisyui: # Disable base theme's CSS
css:
theme:
"dist/css/app.css": false
This ensures the sub-theme's Vite-built CSS replaces (not stacks on) the base theme's CDN/compiled CSS.
Common Mistakes
- Forgetting
npm install && npm run buildafter generating -- The starterkit generates source.pcssfiles. Without the build step,dist/css/app.cssdoes not exist and the theme has no styles. WHY: Unlike the base theme's CDN approach, the starterkit uses a local build pipeline. - Not running
npm run devduring development -- Changes to.pcssfiles, Twig templates, or utility classes are not reflected until rebuilt. WHY: The build step compiles PostCSS/Tailwind and copies component CSS. - Editing
dist/files directly -- Thedist/directory is generated output. Edits will be overwritten on next build. WHY: Edit source files incss/andcomponents/, then rebuild. - Not updating
@sourcedirectives when adding template directories -- Tailwind only scans files matched by@sourcepatterns for class usage. If you add a new directory (e.g.,templates/paragraphs/), add a corresponding@sourcedirective inapp.pcss.css. WHY: Missing@sourceentries cause Tailwind to purge classes it thinks are unused. - Removing
exclude: resetfrom DaisyUI plugin config -- The starterkit excludes DaisyUI's reset because Tailwind 4 provides its own (Preflight). Including both causes style conflicts. WHY: DaisyUI's reset prevents@layer basestyles from being applied.
See Also
drupal-twig-theming.md-- Sub-theme creation patterns- Starterkit issue #3508143 -- Original starterkit feature request