Skip to component content

Choose a complete Bootstrap build or add Moo UI gradually to an existing Bootstrap 5.3 application.

Choose This Path

SituationLoad
New page or whole applicationmoo-ui.css instead of Bootstrap CSS.
Gradual migration in an existing Bootstrap appBootstrap CSS, then moo.css, with a .moo-ui boundary.
Static page without interactive pluginsCSS only.
Bootstrap Dropdown, Modal, Offcanvas, Tooltip, Popover, or ToastKeep Bootstrap's JavaScript bundle and documented initialization.
Chart, Combobox, Context Menu, DataTable, Date Picker, Slider, or SidebarAdd 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.

<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>
npm install @wpmoo/ui bootstrap
import "@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.

<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>
import "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.

<!-- Server-resolved document: no state bootstrap required. -->
<html lang="en" dir="ltr">
  <body>
    <div class="moo-ui" data-bs-theme="dark" data-moo-document-owner="true">
      <!-- Moo UI application -->
    </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.

<!-- Static or strict-CSP fallback: classic script is the root's first child. -->
<div class="moo-ui" data-bs-theme="light" data-moo-theme-key="portal:theme">
  <script src="/vendor/@wpmoo/ui/state.js"></script>
  <!-- embedded Moo UI fragment -->
</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.

@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.

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
import "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.

import 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]")
);

// Later, when the host removes the UI:
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.

import 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.

<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.

Settings

Theme
Choose how Moo UI looks on this device.
Sidebar
Choose how the sidebar sits on the page.
Theme Builder
Tune catalog tokens and chart colors for this browser.
Base color
Theme color
Chart color
Heading font
Body font
Radius
Direction
Choose the reading direction. RTL flips the layout live.
Reset

Restore the default appearance.