Skip to content

Frameworks

IXM Blocks ships Bootstrap-native markup by default. That's Bootstrap Components, the default primitive set every build starts with. But nothing about IXM Blocks' own compositions requires Bootstrap. The real contract is the stable ixm-*/hero__* BEM classes and ixm:* CustomEvents on interactive components; those never change no matter which framework paints the rest of the page.

Using Tailwind today: override the SDC with core's own replaces: key

A theme adopting Tailwind (or anything else) overrides a component using Drupal core's standard SDC mechanism. No custom discovery code, no module fork. Core's ComponentNegotiator resolves every {% include 'ixm_blocks:<component>' %} through whichever component declares replaces: for it, whether Canvas is involved or not: IXM Blocks doesn't do anything special here.

  1. Give the theme's own component any machine name you like, and declare replaces: ixm_blocks:tabs (or whichever component) in its component.yml:

    $schema: https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json
    name: Tabs
    replaces: 'ixm_blocks:tabs'
    
  2. Write the theme's own Twig, replacing the Bootstrap classes with Tailwind utility classes, but keep the BEM classes so the module's own structural CSS and JS keep working:

    {# themes/custom/mytheme/components/tabs/tabs.twig #}
    <ul class="ixm-tabs__nav flex gap-2 border-b" role="tablist">
    
  3. Declare the --theme-* token contract the same way a Bootstrap subtheme does. See Theming. The module's structural CSS reads those tokens regardless of which framework paints the rest of the page, so nothing about theming changes when the framework does.

This is a per-component override: a theme can migrate one component at a time, leaving the rest on the shipped Bootstrap templates while the token bridge keeps every component's look consistent throughout.

Install sdc_prop_inherit so the override doesn't drift

Core requires a replaces: component to declare a prop schema compatible with the one it replaces. Normally that means copying IXM Blocks' whole props block into the theme and keeping it in sync by hand. sdc_prop_inherit removes that copy: it merges the replaced component's prop schema into the override during discovery, so the override's component.yml can declare no props at all (or only the ones it changes). When IXM Blocks adds a prop, the override inherits it automatically, and the theme just adds the markup for it when it's ready.

Bootstrap Components' own primitives are a separate override

The steps above cover IXM Blocks' own compositions (Hero Banner, Statistic, ...). If a Tailwind theme also wants bootstrap_components' primitives (card, accordion, ...) restyled, those need the same per-component template override in that module. IXM Blocks doesn't own them, so it can't route around that module's own markup. See the component-set split for what each module owns.

Swapping a component's JS, not just its markup

replaces: swaps the template. To also swap what runs on the page (say, ixm_blocks:tabs' own ixm-tabs.js for a Tailwind theme's own tab-switching script), declare libraryOverrides in the override's own component.yml, the same key every IXM Blocks component uses for its own assets:

# themes/custom/mytheme/components/tabs/tabs.component.yml
name: Tabs
replaces: 'ixm_blocks:tabs'
libraryOverrides:
  css:
    component:
      tabs.css: {}
  js:
    js/mytheme-tabs.js: {}
  dependencies:
    - core/drupal
    - core/once

Drupal auto-generates a core/components.mytheme--tabs library from this block and attaches it whenever the component renders, the same mechanism components/tabs/tabs.component.yml already uses for js/ixm-tabs.js. Write js/mytheme-tabs.js against core/once the same way (once('my-tab-behavior', '.ixm-tabs__nav', context)), so it stays safe to re-attach under Drupal's AJAX behaviors system instead of double-binding.

SDC structure alongside an existing frontend build

A theme's own frontend build may already own a components/ directory (a Vite-built SCSS/JS bundle, say components/global/{src,public,static}). A Drupal SDC override lives as a sibling directory under the same root. Drupal's discovery just scans every subdirectory of components/ for a *.component.yml; a bundle directory has none, so it's skipped, and the two coexist without conflict:

themes/custom/mytheme/
├── components/
│   ├── global/                    # the theme's own build bundle, unrelated to SDC
│   │   ├── public/
│   │   ├── src/
│   │   └── static/
│   └── tabs/                      # the SDC override, discovered the same way
│       ├── tabs.component.yml
│       ├── tabs.twig
│       ├── tabs.css
│       └── js/
│           └── mytheme-tabs.js