Web Component Libraries & CSS Frameworks

WildflowerJS works with any CSS framework and most JavaScript component libraries out of the box. Because there's no virtual DOM and no build step, you can drop in Bootstrap, DaisyUI, Shoelace, or Chart.js the same way you would in a vanilla HTML page.

The WildflowerJS Advantage:
  • No wrappers needed: unlike React/Vue/Angular, you don't need framework-specific wrapper packages
  • No build step: link a CDN stylesheet or script tag and you're done
  • Real DOM: libraries that read or write the DOM work without conflict
  • Standard HTML: CSS frameworks style your elements directly, no JSX translation needed

Compatibility at a Glance

Third-party libraries fall into three categories based on how they integrate:

Tier Examples Setup Effort
CSS Frameworks
Pure styling, zero JavaScript
DaisyUI, Bootstrap, Bulma, Pico CSS, Open Props, Pure CSS, Beer CSS Link stylesheet Zero config
JS Libraries
DOM-manipulating libraries
Chart.js, SortableJS, Leaflet, FullCalendar, Flatpickr, Tippy.js, Prism.js Script tag + init() Minimal
Web Components
Shadow DOM
Web Awesome, Fluent UI, Nord Health, Shoelace, IBM Carbon Script tag + adapter One script tag

CSS Frameworks (Zero Config)

CSS-only frameworks are the simplest integration. They provide styled components via CSS classes, with no JavaScript runtime, Shadow DOM, or conflicts. WildflowerJS's data-bind-class and data-bind-style work perfectly with them.

Recommended: DaisyUI + Tailwind CSS

DaisyUI provides 65+ pre-styled components (buttons, cards, modals, tabs, drawers, etc.) as pure CSS classes built on Tailwind CSS. It has 40K+ GitHub stars, active development, and zero JavaScript overhead.

<!-- Add to your <head> -->
<link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>

<!-- That's it. Now use DaisyUI classes with WildflowerJS: -->
<div data-component="todo-app">
    <div class="card bg-base-100 shadow-xl">
        <div class="card-body">
            <h2 class="card-title" data-bind="title"></h2>

            <input type="text" class="input input-bordered w-full"
                   data-model="newItem" placeholder="Add a task...">
            <button class="btn btn-primary mt-2" data-action="addItem">
                Add Task
            </button>

            <div class="divider"></div>

            <div data-list="items">
                <template>
                    <div class="flex items-center gap-2 py-1">
                        <input type="checkbox" class="checkbox checkbox-sm"
                               data-action="toggleItem">
                        <span data-bind="text"
                              data-bind-class="done ? 'line-through opacity-50' : ''">
                        </span>
                        <button class="btn btn-ghost btn-xs ml-auto"
                                data-action="removeItem">
                            &times;
                        </button>
                    </div>
                </template>
            </div>

            <div class="stats shadow mt-4">
                <div class="stat">
                    <div class="stat-title">Remaining</div>
                    <div class="stat-value text-primary" data-bind="remaining"></div>
                </div>
            </div>
        </div>
    </div>
</div>
wildflower.component('todo-app', {
    state: {
        title: 'My Tasks',
        newItem: '',
        items: [
            { text: 'Learn WildflowerJS', done: true },
            { text: 'Try DaisyUI components', done: false },
            { text: 'Build something cool', done: false }
        ]
    },

    computed: {
        remaining() {
            return this.items.filter(i => !i.done).length;
        }
    },

    addItem() {
        if (!this.newItem.trim()) return;
        this.items = [
            ...this.items,
            { text: this.newItem, done: false }
        ];
        this.newItem = '';
    },

    toggleItem(event, element) {
        const index = element.closest('[data-list-index]')?.dataset.listIndex;
        if (index !== undefined) {
            const items = [...this.items];
            items[index] = { ...items[index], done: !items[index].done };
            this.items = items;
        }
    },

    removeItem(event, element) {
        const index = element.closest('[data-list-index]')?.dataset.listIndex;
        if (index !== undefined) {
            this.items = this.items.filter((_, i) => i !== parseInt(index));
        }
    }
});
Why DaisyUI works perfectly: It's pure CSS. The btn, card, input, checkbox, stats classes style standard HTML elements. WildflowerJS handles reactivity; DaisyUI handles appearance. No conflicts possible.

Other CSS Frameworks

Framework Components Approach CDN Size Notes
Bootstrap 5 30+ CSS classes + optional JS ~25 KB Most popular. JS components (dropdowns, modals) work alongside WildflowerJS.
Bulma 40+ Pure CSS ~26 KB Zero JavaScript. Flexbox-based. Clean, modern aesthetic.
Pico CSS 20+ Classless + semantic ~10 KB Styles native HTML elements automatically. Minimal classes needed.
Open Props N/A CSS custom properties ~5 KB Design tokens, not components. Pairs with any approach.
Pure CSS 15+ Pure CSS modules ~4 KB Yahoo's minimal CSS toolkit. Extremely lightweight.
Beer CSS 40+ CSS classes ~15 KB Material Design 3 aesthetic. No build tools required.

All of these work by adding a <link> tag to your page. There are no adapters to write and nothing to configure.

JavaScript Libraries (Minimal Setup)

DOM-manipulating JavaScript libraries, such as charting, mapping, drag-and-drop, date pickers, and rich text editors, integrate naturally because WildflowerJS uses the real DOM. Initialize them in your component's init() method and sync state changes with watchers.

Full integration guide: See Third-Party Library Integration for detailed examples with SortableJS, Leaflet, FullCalendar, Chart.js, and the general integration pattern.

The General Pattern

wildflower.component('my-widget', {
    state: {
        libraryInstance: null,
        data: []
    },

    init() {
        // 1. Initialize the library on a DOM element
        this.libraryInstance = new SomeLibrary(
            this.element.querySelector('#target'),
            { data: this.data }
        );

        // 2. Library events → update WildflowerJS state
        this.libraryInstance.on('change', (newData) => {
            this.data = newData;
        });

        // 3. WildflowerJS state changes → update library.
        // subscribe() is the imperative form, and it belongs here because the
        // library has to exist before the handler can call it. It returns an
        // unsubscribe function; keep it for destroy().
        this._unsubscribe = this.subscribe('data', (newData) => {
            this.libraryInstance.setData(newData);
        });
    },

    // 4. Clean up on destroy
    destroy() {
        if (this._unsubscribe) this._unsubscribe();
        if (this.libraryInstance) {
            this.libraryInstance.destroy();
        }
    }
});

A top-level watch: { data() { ... } } block does the same job declaratively and is the better choice when the handler does not depend on something built during init(). Assigning this.watch is not an option in either case: watchers are registered before init() runs, and watch is not part of the component context, so the assignment is silently ignored.

Libraries Known to Work Well

Library Purpose Integration Notes
SortableJS Drag-and-drop sorting Works with both data-list and manual rendering. See Third-Party Integration.
Leaflet Interactive maps Call wildflower.scan() after popup opens to initialize components inside map popups.
Chart.js Charts and graphs Canvas-based. State changes update chart via chart.update().
FullCalendar Calendar UI Use watchers to sync events between state and calendar instance.
Flatpickr Date/time picker Enhances a standard <input>. Works with data-model directly.
Tippy.js Tooltips & popovers Attaches to existing DOM elements. No conflicts with data bindings.
Quill / TinyMCE Rich text editors Initialize on a container element. Sync content via editor events.
Prism.js / Highlight.js Code highlighting Call after dynamic content loads. This documentation site uses Prism.js.

Web Component Libraries

Web Component libraries like Web Awesome, Fluent UI, Nord Health, Shoelace, and IBM Carbon provide rich, pre-built UI widgets as custom HTML elements with Shadow DOM encapsulation.

Smart Defaults: Most Libraries Work Automatically

WildflowerJS auto-detects custom elements and their capabilities. For any element with a hyphen in its tag name, the framework automatically:

  1. Detects the property: if the element has a boolean checked property, it uses checked; otherwise it uses value
  2. Listens for native events: listens for both input and change events, which covers text inputs, selects, and booleans
  3. Protects Shadow DOM: never sets textContent on custom elements, preventing light DOM destruction

These smart defaults handle simple cases automatically. For production use, load the library's adapter pack, a single script tag that adds precise ready-timing (Lit's updateComplete) and explicit property mapping for every widget in the library.

Custom Elements Everywhere: WildflowerJS passes the Custom Elements Everywhere tests, the community benchmark for framework compatibility with Web Components. The tests cover rendering custom elements, passing data via attributes and properties, and handling DOM events. Run our test suite →
Library Events Adapter Needed? Setup
Web Awesome 3.x Native input / change Yes Load library + adapter
Fluent UI v2 Native input / change Yes Load library + adapter
Nord Health Native input / change Yes Load library + adapter
Shoelace 2.x Prefixed: sl-input / sl-change Yes Load library + adapter
IBM Carbon v2 Custom: cds-*-changed / cds-*-selected Yes Load library + adapter
Rule of thumb: All Lit/FAST-based Web Component libraries need an adapter, even if they fire native events, because the adapter handles property mapping (e.g. checked vs value) and the async updateComplete lifecycle. Each adapter is a single script tag.

With Adapter (Web Awesome)

<!-- Load Web Awesome from CDN -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@aspect.me/webawesome@3/dist-cdn/styles/themes/default.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@aspect.me/webawesome@3/dist-cdn/webawesome.loader.js"></script>

<!-- Load WildflowerJS + adapter -->
<script src="wildflower.min.js"></script>
<script src="adapters/web-awesome.js"></script>

<!-- Use data-model as normal: -->
<div data-component="form-demo">
    <wa-input label="Name" data-model="name"></wa-input>
    <wa-select label="Role" data-model="role">
        <wa-option value="dev">Developer</wa-option>
        <wa-option value="design">Designer</wa-option>
    </wa-select>
    <wa-checkbox data-model="agreed">I agree</wa-checkbox>
    <p>Hello, <span data-bind="name"></span>!</p>
</div>
Web Component Example Web Awesome: Two-Way Binding, Multiple Widgets, Presets Open Full Example

With Adapter (Nord Health)

Nord Health is an enterprise design system with input, select, textarea, checkbox, toggle, and range components. Like Web Awesome, it uses an adapter pack for two-way binding.

Web Component Example Nord Health: Two-Way Binding, Multiple Widgets, Presets Open Full Example

With Adapter (Shoelace)

<!-- 1. Load the component library -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace/cdn/themes/light.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace/cdn/shoelace-autoloader.js"></script>

<!-- 2. Load WildflowerJS -->
<script src="wildflower.min.js"></script>

<!-- 3. Load the adapter pack -->
<script src="wildflower-adapters/shoelace.js"></script>

<!-- 4. Use Web Components with data-model, just like native inputs -->
<div data-component="contact-form">
    <sl-input label="Name" data-model="name"></sl-input>
    <sl-input label="Email" type="email" data-model="email"></sl-input>
    <sl-textarea label="Message" data-model="message"></sl-textarea>
    <sl-checkbox data-model="subscribe">Subscribe to newsletter</sl-checkbox>
    <sl-button variant="primary" data-action="submit">Send</sl-button>

    <p>Preview: <span data-bind="name"></span> (<span data-bind="email"></span>)</p>
</div>
wildflower.component('contact-form', {
    state: {
        name: '',
        email: '',
        message: '',
        subscribe: false
    },

    submit() {
        console.log('Form data:', {
            name: this.name,
            email: this.email,
            message: this.message,
            subscribe: this.subscribe
        });
    }
});
Notice: The component code is identical to what you'd write with native HTML inputs. The adapter handles the translation behind the scenes.

Registering Custom Adapters

For Web Components not covered by an existing adapter pack, register adapters manually:

// Register a single adapter
wildflower.registerAdapter('my-custom-input', {
    prop: 'value',        // The JS property to read/write
    event: 'value-changed' // The event that fires on user interaction
});

// Now data-model works with <my-custom-input>
// <my-custom-input data-model="username"></my-custom-input>

The data-model-event Override

For one-off situations where you need a specific event name without registering a full adapter:

<!-- Override the event name directly on the element -->
<fancy-datepicker data-model="selectedDate" data-model-event="date-selected">
</fancy-datepicker>

Resolution order: data-model-event attribute > registered adapter > smart default (native input + change).

Adapter Packs

WildflowerJS ships adapter packs for popular Web Component libraries. Each adapter maps the library's property names, event names, and async lifecycle (e.g. Lit's updateComplete). Drop in a single <script> tag after the framework.

Some libraries also use non-standard event names, which the adapter handles:

Library Prefix Why Adapter Needed Adapter File Components Covered
Shoelace 2.x sl-* Uses prefixed events (sl-input, sl-change) adapters/shoelace.js 9 (input, textarea, select, radio-group, checkbox, switch, range, rating, color-picker)
IBM Carbon v2 cds-* Per-component custom events (cds-dropdown-selected, cds-checkbox-changed, etc.) adapters/carbon.js 12 (text-input, textarea, password-input, search, number-input, select, dropdown, combo-box, checkbox, toggle, radio-button-group, slider)

CDN Drop-in Usage

<!-- Shoelace (needs adapter, prefixed events) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/themes/dark.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/shoelace-autoloader.js"></script>
<script src="wildflower.min.js"></script>
<script src="adapters/shoelace.js"></script>

<!-- IBM Carbon (needs adapter, per-component custom events) -->
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/tag/v2/latest/text-input.min.js"></script>
<script type="module" src="https://1.www.s81c.com/common/carbon/web-components/tag/v2/latest/dropdown.min.js"></script>
<script src="wildflower.min.js"></script>
<script src="adapters/carbon.js"></script>

<!-- Web Awesome -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@aspect.me/webawesome@3/dist-cdn/styles/themes/default.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@aspect.me/webawesome@3/dist-cdn/webawesome.loader.js"></script>
<script src="wildflower.min.js"></script>
<script src="adapters/web-awesome.js"></script>

<!-- Fluent UI -->
<script type="module" src="https://cdn.jsdelivr.net/npm/@fluentui/web-components@2.6.1/dist/web-components.min.js"></script>
<script src="wildflower.min.js"></script>
<script src="adapters/fluent-ui.js"></script>

<!-- Nord Health -->
<link rel="stylesheet" href="https://nordcdn.net/ds/css/4.2.0/nord.min.css">
<link rel="stylesheet" href="https://nordcdn.net/ds/tokens/8.0.0/tokens.custom-properties.css">
<script type="module" src="https://nordcdn.net/ds/components/4.11.0/index.js"></script>
<script src="wildflower.min.js"></script>
<script src="adapters/nord.js"></script>

Manual Adapter Registration

If you encounter a Web Component not covered by an existing adapter pack, register an adapter manually:

// Register an adapter for a custom element
wildflower.registerAdapter('my-custom-input', {
    prop: 'value',         // JS property to read/write
    event: 'value-changed' // Event that fires on user interaction
});
// WildflowerJS auto-detects 'value' vs 'checked' and listens for both events.

Choosing the Right Approach

Use this decision tree to pick the right integration tier for your project:

Just need styled components? Use a CSS framework (DaisyUI, Bootstrap, Bulma).
Zero JavaScript overhead, zero conflicts, zero configuration.
Need rich interactive widgets? (charts, maps, drag-and-drop, date pickers)
Use vanilla JS libraries. Initialize in init(), sync with watchers.
See Third-Party JS Libraries for integration patterns.
Want a full design system with 50+ pre-built widgets? (like Vuetify for Vue)
Use Web Awesome, Fluent UI, Nord, Shoelace, or IBM Carbon. Each needs a one-line adapter script tag for two-way binding.

Mixing Approaches

These tiers are not mutually exclusive. A typical production app might use:

<!-- Tailwind/DaisyUI for layout and basic styling -->
<link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet">

<!-- Shoelace for rich form widgets -->
<script type="module" src="https://cdn.jsdelivr.net/.../shoelace-autoloader.js"></script>

<!-- Chart.js for data visualization -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<!-- WildflowerJS ties it all together -->
<script src="wildflower.min.js"></script>
<script src="wildflower-adapters/shoelace.js"></script>

DaisyUI handles your card layouts and button styles. Shoelace provides a polished date picker and color picker. Chart.js renders your dashboard graphs. WildflowerJS manages the reactive state that drives all of them.

Full Web Component Bridge

Web Awesome 3.x custom elements behind Shadow DOM, wired up with wildflower.registerAdapter(). Two-way data-model binding across six widget types.

Mini Integration Showcase

A trip planner combining interactive maps, calendar scheduling, and sortable task lists, with no wrapper packages.