Skip to content

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.css files and creates Rollup entries
  • Component CSS copy: viteStaticCopy copies dist/css/components/ to components/ so each SDC can reference its own compiled CSS
  • SVG sprite: @pivanov/vite-plugin-svg-sprite generates default-icons.svg from icons/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:

  1. 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
  1. 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 build after generating -- The starterkit generates source .pcss files. Without the build step, dist/css/app.css does 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 dev during development -- Changes to .pcss files, 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 -- The dist/ directory is generated output. Edits will be overwritten on next build. WHY: Edit source files in css/ and components/, then rebuild.
  • Not updating @source directives when adding template directories -- Tailwind only scans files matched by @source patterns for class usage. If you add a new directory (e.g., templates/paragraphs/), add a corresponding @source directive in app.pcss.css. WHY: Missing @source entries cause Tailwind to purge classes it thinks are unused.
  • Removing exclude: reset from 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 base styles from being applied.

See Also