CSS-first, not JavaScript-free
Bootstrap owns its native plugins. Moo UI adds optional, explicitly initialized ESM only for documented behavior gaps.
Choose This Path
| Situation | Load |
| New page or whole application | moo-ui.css instead of Bootstrap CSS. |
| Gradual migration in an existing Bootstrap app | Bootstrap CSS, then moo.css, with a .moo-ui boundary. |
| Static page without interactive plugins | CSS only. |
| Bootstrap Dropdown, Modal, Offcanvas, Tooltip, Popover, or Toast | Keep Bootstrap's JavaScript bundle and documented initialization. |
| Chart, Combobox, Context Menu, DataTable, Date Picker, Slider, or Sidebar | Add only the corresponding optional Moo UI ESM module. |
Full Build
moo-ui.css is a complete Bootstrap CSS build with Moo UI
defaults. Do not load another Bootstrap stylesheet beside it.
It emits Moo UI's root/body theme tokens before Bootstrap's reboot
body rules. If the page persists dark mode or resolves the user's
system color scheme, set data-bs-theme synchronously in
the head before loading this stylesheet.
1
2
3<link rel="stylesheet" href="https://unpkg.com/@wpmoo/ui@1.3.0/dist/assets/css/moo-ui.css">
<button type="button" class="btn btn-primary">Create workspace</button>
1npm install @wpmoo/ui bootstrap
1import "@wpmoo/ui/moo-ui.css";
Scoped Gradual Adoption
In an existing Bootstrap app, keep Bootstrap CSS first, load the
scoped Moo UI component layer second, and wrap only migrated regions in
.moo-ui.
1
2
3
4
5
6<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://unpkg.com/@wpmoo/ui@1.3.0/dist/assets/css/moo.css">
<div class="moo-ui">
<button type="button" class="btn btn-primary">Create workspace</button>
</div>
1
2import "bootstrap/dist/css/bootstrap.min.css";
import "@wpmoo/ui/moo.css";
Theme Ownership And First Paint
Resolve the initial light or dark value on
the server whenever the host knows the request or session
preference. Put that value on the first application owner and keep
the document language and default direction on html.
A server-resolved document does not need a state bootstrap.
1
2
3
4
5
6
7
8
<html lang="en" dir="ltr">
<body>
<div class="moo-ui" data-bs-theme="dark" data-moo-document-owner="true">
</div>
</body>
</html>
For a static or strict-CSP host without an inline bootstrap, keep the
classic external asset as the first child of the resolved owner. An
embedded owner uses explicit persistence keys so it does not claim
the document's storage or direction.
1
2
3
4
5
<div class="moo-ui" data-bs-theme="light" data-moo-theme-key="portal:theme">
<script src="/vendor/@wpmoo/ui/state.js"></script>
</div>
On a first visit, a static host without a known server preference can
only correct a browser-only preference after the external asset is
fetched, so that correction may be visible. The server value remains
the deterministic fallback; an external fetch cannot guarantee zero
flash in this profile. RC9 exposes @wpmoo/ui/state.js.
Sass Source
Moo UI ships documented Sass source entrypoints for hosts that compile
their own stylesheet. Import Bootstrap functions first, import Moo UI's
scss/config next, place supported token overrides after
that config import, then import the full or scoped Moo UI source
entrypoint.
1
2
3
4
5
6@import "bootstrap/scss/functions";
@import "@wpmoo/ui/scss/config";
$primary: #3b82f6;
@import "@wpmoo/ui/scss/moo-ui";
Bootstrap JavaScript
Moo UI does not replace or automatically initialize Bootstrap
plugins. Load Bootstrap's bundle when your page uses plugin-owned
behavior; Tooltip and Popover still require Bootstrap's documented
opt-in initialization.
1<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
1import "bootstrap/dist/js/bootstrap.bundle.min.js";
Optional Moo UI ESM
Chart, Combobox, Context Menu, DataTable, Date Picker, Slider, and
Sidebar cover behavior gaps Bootstrap does not provide. Their modules
are side-effect-free: importing them does not scan the document.
Create instances explicitly and dispose them when your host removes
the corresponding UI.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38import Chart from "@wpmoo/ui/chart.js";
import Combobox from "@wpmoo/ui/combobox.js";
import ContextMenu from "@wpmoo/ui/context-menu.js";
import DataTable from "@wpmoo/ui/datatable.js";
import Datepicker from "@wpmoo/ui/datepicker.js";
import Sidebar from "@wpmoo/ui/sidebar.js";
import Slider from "@wpmoo/ui/slider.js";
const chart = Chart.getOrCreateInstance(
document.querySelector(".chart")
);
const combobox = Combobox.getOrCreateInstance(
document.querySelector(".combobox")
);
const contextMenu = ContextMenu.getOrCreateInstance(
document.querySelector(".context-menu")
);
const datatable = DataTable.getOrCreateInstance(
document.querySelector(".datatable")
);
const datepicker = Datepicker.getOrCreateInstance(
document.querySelector("[data-datepicker]")
);
const sidebar = Sidebar.getOrCreateInstance(
document.querySelector('[data-layout="app"][data-slot="sidebar-wrapper"]')
);
const slider = Slider.getOrCreateInstance(
document.querySelector("[data-slider]")
);
chart.dispose();
combobox.dispose();
contextMenu.dispose();
datatable.dispose();
datepicker.dispose();
sidebar.dispose();
slider.dispose();
If your host prefers one Moo UI module request, use the
side-effect-free aggregate instead of importing each module
separately.
The aggregate fetches Chart and its Chart.js runtime only when
loadChart() is called. This asynchronous loader replaces
the aggregate's synchronous Chart export in RC10;
standalone Chart imports keep their existing API.
1
2
3
4
5
6
7
8
9
10import MooUI, { Combobox, DataTable } from "@wpmoo/ui/moo-ui.js";
Combobox.getOrCreateInstance(document.querySelector(".combobox"));
MooUI.DataTable.getOrCreateInstance(document.querySelector(".datatable"));
const chartRoot = document.querySelector(".chart");
if (chartRoot) {
const Chart = await MooUI.loadChart();
Chart.getOrCreateInstance(chartRoot);
}
Both aggregate variants load the adjacent chart.js asset.
Keep that file beside the aggregate when self-hosting. Repeated loader
calls share the same constructor and module cache. A failed fetch
rejects the loader promise; the host can handle that error in its
existing loading flow.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44<script type="module">
import Chart from "https://cdn.jsdelivr.net/npm/@wpmoo/ui@1.3.0/dist/js/chart.js";
import Combobox from "https://cdn.jsdelivr.net/npm/@wpmoo/ui@1.3.0/dist/js/combobox.js";
import ContextMenu from "https://cdn.jsdelivr.net/npm/@wpmoo/ui@1.3.0/dist/js/context-menu.js";
import DataTable from "https://cdn.jsdelivr.net/npm/@wpmoo/ui@1.3.0/dist/js/datatable.js";
import Datepicker from "https://cdn.jsdelivr.net/npm/@wpmoo/ui@1.3.0/dist/js/datepicker.js";
import Sidebar from "https://cdn.jsdelivr.net/npm/@wpmoo/ui@1.3.0/dist/js/sidebar.js";
import Slider from "https://cdn.jsdelivr.net/npm/@wpmoo/ui@1.3.0/dist/js/slider.js";
const chartRoot = document.querySelector(".chart");
if (chartRoot) {
Chart.getOrCreateInstance(chartRoot);
}
const comboboxRoot = document.querySelector(".combobox");
if (comboboxRoot) {
Combobox.getOrCreateInstance(comboboxRoot);
}
const contextMenuRoot = document.querySelector(".context-menu");
if (contextMenuRoot) {
ContextMenu.getOrCreateInstance(contextMenuRoot);
}
const datatableRoot = document.querySelector(".datatable");
if (datatableRoot) {
DataTable.getOrCreateInstance(datatableRoot);
}
const datepickerRoot = document.querySelector("[data-datepicker]");
if (datepickerRoot) {
Datepicker.getOrCreateInstance(datepickerRoot);
}
const sidebarRoot = document.querySelector('[data-layout="app"][data-slot="sidebar-wrapper"]');
if (sidebarRoot) {
Sidebar.getOrCreateInstance(sidebarRoot);
}
const sliderRoot = document.querySelector("[data-slider]");
if (sliderRoot) {
Slider.getOrCreateInstance(sliderRoot);
}
</script>
Review the Chart,
Combobox,
Context Menu,
Data Table,
Date Picker,
Slider,
and Sidebar
pages for their HTML, selectors, lifecycle, and Bootstrap ownership.
Minified browser module URLs are also published for Chart and Date
Picker, plus the aggregate bundle, at
https://cdn.jsdelivr.net/npm/@wpmoo/ui@1.3.0/dist/js/chart.min.js,
https://cdn.jsdelivr.net/npm/@wpmoo/ui@1.3.0/dist/js/datepicker.min.js, and
https://cdn.jsdelivr.net/npm/@wpmoo/ui@1.3.0/dist/js/moo-ui.min.js.
Troubleshooting
- Styles look duplicated or inconsistent
- Use either the full
moo-ui.css build or Bootstrap CSS followed by scoped moo.css; do not combine both Moo UI stylesheets.
- Scoped styles do not appear
- Confirm
moo.css loads after Bootstrap CSS and the migrated markup has a .moo-ui ancestor.
- A Bootstrap control does not open
- Load Bootstrap's bundle and follow that plugin's documented initialization requirements.
- A Moo UI ESM module does not respond
- Import the matching Moo UI ESM module (Chart, Combobox, Context Menu, DataTable, Date Picker, Slider, or Sidebar) and initialize the rendered root explicitly; imports never auto-scan.