The combination of Single Directory Components and UIkit 3 provides a solid foundation for component-driven Drupal theming. But how do you actually set up such a theme? This article walks through the practical steps: from theme skeleton to your first working component.
Theme skeleton: clear directory structure
Start with a minimal theme structure that leaves room for growth:
mytheme/
├── mytheme.info.yml
├── mytheme.libraries.yml
├── mytheme.theme
├── components/
│ └── alert/
│ ├── alert.component.yml
│ ├── alert.twig
│ └── alert.css
├── templates/
└── assets/
├── uikit/
├── css/
└── js/
The components/ directory is the heart of your SDC setup. Drupal automatically scans it and makes components available via Twig ({% include 'mytheme:alert' %}). Classic template overrides remain in templates/, allowing both approaches to coexist.
Integrating UIkit as foundation library
Load UIkit as a global library in mytheme.libraries.yml:
global-styling:
css:
theme:
assets/uikit/css/uikit.min.css: {}
assets/css/custom.css: {}
js:
assets/uikit/js/uikit.min.js: {}
assets/uikit/js/uikit-icons.min.js: {}
Activate the library in mytheme.info.yml:
name: My Theme
type: theme
base theme: false
core_version_requirement: ^10 || ^11
libraries:
- mytheme/global-styling
Download UIkit directly from getyikit.com and place the files in assets/uikit/. This keeps dependencies local and avoids external CDN dependencies—important for projects with strict CSP policies or compliance requirements.
Your first component: a Card
A card component illustrates the collaboration between SDC and UIkit. Start with components/card/card.component.yml:
'$schema': https://git.drupalcode.org/project/drupal/-/raw/11.x/core/modules/sdc/src/metadata.schema.json
name: Card
status: stable
props:
type: object
properties:
title:
type: string
title: Card title
image_url:
type: string
title: Image URL
variant:
type: string
title: Card variant
enum:
- default
- primary
- secondary
default: default
slots:
content:
title: Card content
The component schema explicitly defines what a card can and should do. This prevents ad-hoc modifications and makes the API transparent to other developers.
Markup in card.twig:
{% set variant_class = variant != 'default' ? 'uk-card-' ~ variant : '' %}
<div class="uk-card uk-card-body {{ variant_class }}">
{% if image_url %}
<div class="uk-card-media-top">
<img src="{{ image_url }}" alt="{{ title }}">
</div>
{% endif %}
{% if title %}
<h3 class="uk-card-title">{{ title }}</h3>
{% endif %}
<div class="uk-card-content">
{% block content %}{% endblock %}
</div>
</div>
UIkit styling works immediately—no additional CSS needed. Place component-specific overrides in card.css, only when necessary.
Using components in Drupal
SDC components support two syntax styles depending on your Drupal version.
Modern syntax (Drupal 10.3+, recommended)
The component tag syntax is cleaner and reads like HTML:
<twig:mytheme:card title="Project title" variant="primary">
<twig:block name="content">
<p>Project description goes here.</p>
</twig:block>
</twig:mytheme:card>
For node templates (node--article.html.twig):
<twig:mytheme:card
title="{{ node.title.value }}"
variant="secondary"
image_url="{{ file_url(node.field_image.entity.uri.value) }}">
<twig:block name="content">
{{ content.body }}
</twig:block>
</twig:mytheme:card>
Classic syntax (all versions)
For components without slots, use {% include %}:
{% include 'mytheme:button' with {
label: 'Read more',
url: node.url
} only %}
For components with slots or blocks, use {% embed %}:
{% embed 'mytheme:card' with {
title: 'Project title',
variant: 'primary'
} only %}
{% block content %}
<p>Project description goes here.</p>
{% endblock %}
{% endembed %}
Important: {% include %} is self-closing and cannot contain blocks. Use {% embed %} when you need to fill slots with dynamic content.
The only keyword isolates scope and prevents variables from the parent template leaking into the component. This keeps components predictable.
Component library dependency management
When a component needs specific UIkit modules (such as lightbox or slideshow), define a library in mytheme.libraries.yml:
component.gallery:
css:
component:
components/gallery/gallery.css: {}
js:
assets/uikit/js/components/lightbox.min.js: {}
dependencies:
- mytheme/global-styling
Download additional UIkit components from getyikit.com and place them in your assets/uikit/js/components/ directory alongside the core files. This maintains consistency with your self-hosted approach.
Link the library to your component in gallery.component.yml:
libraryOverrides:
dependencies:
- mytheme/component.gallery
Drupal automatically loads the library when the component is used. This prevents unnecessary asset loading on pages without galleries.
Naming and organization
Maintain consistent naming conventions:
- Component name: lowercase, descriptive (
card,hero-banner,author-bio) - Props: camelCase (
imageUrl,showMeta) - Variants: lowercase strings (
primary,secondary,large)
Group related components in subfolders as your theming grows:
components/
├── content/
│ ├── card/
│ ├── teaser/
│ └── author-bio/
├── navigation/
│ ├── main-menu/
│ └── breadcrumb/
└── layout/
├── hero/
└── section/
This keeps your component library organized and makes code reviews more efficient.
Development workflow: cache clearing
SDC discovery cache often needs clearing during development. Three options:
- Drush:
drush cr - Admin UI: Configuration > Development > Clear all caches
- Null coalescing in settings.local.php for automatic discovery
For active development:
# settings.local.php
$settings['cache']['bins']['discovery'] = 'cache.backend.null';
This ensures component changes are immediately visible without cache clearing.
Security and Content Security Policy
Self-hosted UIkit assets significantly simplify CSP configuration. Your CSP header can remain restrictive:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';
The unsafe-inline for styles is needed for UIkit's dynamic positioning, but scripts remain limited to your own domain. This is a major advantage over setups with external CDN dependencies.
Conclusion and next steps
You now have a working SDC + UIkit setup with clear separation between foundation and project layer. This foundation provides room to grow: add new components, extend existing ones, or gradually migrate legacy templates.
In a follow-up article, we'll dive into more complex patterns: form integration, Views rendering, Layout Builder support, and component composition.
For a production setup: thoroughly test your components in different contexts, document your component API for team members, and set up monitoring to optimize asset loading.