# Table of contents Mono-repo orchestration + Lit web components for the Vue ecosystem. > **Read first:** [Migration: `mono-*` → `@mono-lit/*`](https://mono-libs.netlify.app/migration.md). The packages were renamed (`mono-helper` → `@mono-lit/helper`, `mono-utils` → `@mono-lit/utility`, `mono-devextreme` → `@mono-lit/devextreme`) and every page below uses the new names. If the code you are working on still imports `mono-*`, migrate it before following any other page. ## Recommended reading order (for AI) Read these first, in order: 1. [Migration: `mono-*` → `@mono-lit/*`](https://mono-libs.netlify.app/migration.md): READ THIS FIRST. The packages were renamed: mono-helper → @mono-lit/helper, mono-utils → @mono-lit/utility (not utils), mono-devextreme → @mono-lit/devextreme, all public on npmjs. Every other page uses the new names; if the code you are working on still imports mono-*, migrate it with this guide before following any other page. 2. [Template Rules (for AI)](https://mono-libs.netlify.app/ai/template.md): This project is built to be maintained largely by an AI assistant. This page is the contract the AI follows when it writes code in a Host or a Remote. An app is written in one of the ecosystem's template languages — Vue or Nuxt — declared by the type field in its mono.config.ts, and part of these rules depends on which one you're in. The rules are grouped into: Common Rules: General — language-neutral; apply to every app, Host or Remote. Common Rules: Vue / Nuxt — one section per template language. Read only the one matching your app's type. Host template — what the AI may / may not do in the Host. Remote template — what the AI may / may not do in a Remote. Start here and follow the General rules on every change, plus your language's section. New to the repo? Setup shows how a Host (Vue or Nuxt) and a Remote are wired together. This page is the day-to-day contract for writing code inside them. The General rules are ordered as a learning path: understand the machine (1–4) → shared config (5) → naming conventions (6) → environment (7) → where code & data live (8–10) → capabilities: pick a component flavor, fetch, type, and build UI (11–15) → put it together and build a feature (16) → remember what you learned (17). Read top-to-bottom once, then read your language section top-to-bottom once; after that, route by the tables below. 3. [Reference LLMs (for AI)](https://mono-libs.netlify.app/ai/ref-llms.md): When you (an AI assistant) need authoritative API details for any library in this stack, read that project's official llms.txt first instead of relying on memory. An llms.txt is the project's documentation flattened into a single machine-readable markdown file, so it is the most reliable source for current APIs, options, and examples. How to use Fetch the relevant URL below and ground your answer in it before writing or changing code. Prefer the llms.txt reference over assumptions, and base the behavior you rely on on what the docs actually say. 4. [Getting Started](https://mono-libs.netlify.app/repo/getting-started.md): Mono-repo is a way to build a large-scale, modular front-end on top of the Vue ecosystem. The goal is a maintainable, clean, complex project that takes minimum effort to grow. 5. [Setup](https://mono-libs.netlify.app/repo/setup.md): The mono ecosystem has two roles: a Host and one or more Remotes (see Getting Started). The Host owns the shared shell; a Remote adds its own pages and plugs into it. A Host is Vue or Nuxt — and the Host and its Remote must have the same framework. A Vue pair (Vue Host + Vue Remote) is joined by mono's own merge; a Nuxt pair (Nuxt Host + Nuxt Remote) by Nuxt's own layer system. Those two choices are the only things that change between setups. The hybrid pairing — possible, but at your own risk Joining a Nuxt Host with a Vue Remote is still possible: mono's compat layer (mono.nuxt()) rewrites the host's Nuxt code so the Remote's Vite build can run it, so nothing blocks the combination. It is outside the standard — the Remote ships as a client-rendered island inside a server-rendered shell, and the seams that compat layer papers over (SSR, layouts, routing, state) remain your risk. Prefer the same-framework pairing; take the hybrid only when you knowingly accept those risks. 6. [Config](https://mono-libs.netlify.app/repo/config.md): The mono-repo also comes with a config file that handles cookies, JWT state, and deployment. Apps living side by side in one repository? apps[].path reads a sibling folder in place — see Mono-Lith → Config. The config lives in a single file: 7. [Environment](https://mono-libs.netlify.app/repo/env.md): Each app keeps its own environment variables. mono-env is a small wrapper (shipped with @mono-lit/utility) that loads a root env file first, then layers each app's env file from .mono/apps/ on top, and finally runs your real command (vite, vue-tsc, …) with the merged environment. You don't call it directly — it's already wired into every script in the templates, so day to day you just run pnpm dev / pnpm build. That single line means: load .env.dev (root, then every app), then run vite. 8. [Data Fetching](https://mono-libs.netlify.app/repo/data-fetching.md): To set up fetching, you need three things: a base URL, a token, and the fetch function. Mono ships with all of them. Inside your mono.config.ts, define a fetching object. api is a named map — each entry has a required url, a type ('restful' or 'odata'), and, for odata, its own oDataService. The shared DevExtreme source constructors (dataSource / oDataStore / customStore) are the same for every odata API, so they live once on fetching.source: You then pick which named API a call uses with configBaseUrl: "". It resolves that entry's url (and, for odata, the shared source ctors plus that entry's oDataService), overriding the call's own baseUrl. 9. [DataSource](https://mono-libs.netlify.app/odata/datasource.md): A DataSource is a live, reactive handle to a remote OData endpoint that mono's table, select and dropdown-table components consume — it loads rows on demand (one page at a time), exposes paging, filtering and sorting, and re-renders the bound component whenever its data changes. 10. [Resolvers](https://mono-libs.netlify.app/repo/resolvers.md): A resolver is a host-side stopgap fix. When a remote app has a bug or isn't ready yet and it breaks the host, the host doesn't wait for the upstream fix — it drops a small file in a resolvers/ folder, wires it in, and deletes it once the remote is fixed upstream. Resolvers live only in the host; the remote stays untouched. 11. [Template Changelog](https://mono-libs.netlify.app/repo/template-changelog.md): Changes to the starter templates, grouped by change. Each shows the commit and the exact lines to remove (red) / add (green). 12. [Getting started](https://mono-libs.netlify.app/ui/getting-started.md): Mono UI is a small set of Lit-powered web components — , , , etc. They're standard custom elements, so they work in Vue, React, Svelte, plain HTML, or anywhere custom elements run. 13. [Theme](https://mono-libs.netlify.app/ui/theme.md): @mono-lit/helper's look is Basecoat UI's. Three independent axes, all plain CSS classes on a root element, all crossing the shadow boundary through inherited custom properties: They are orthogonal: Nova metrics with the ONE palette in dark mode is a valid combination. Components don't change — once a class is active, every mono- component (light and shadow build alike) picks it up. ONE (the EJI ONE design system) is the default: index.css applies it at :root, nothing to import or class. The other eight flavors are Basecoat's eight styles, mirrored 1:1: a style never touches tokens or fonts, only how each component is sized and shaped. Each ships as its own sheet — a vega base that resets what ONE sets, then the style's own overrides for the knobs ported components read (--mono-control-height-, --mono-button--radius, --mono-input-outline-*, …). Ported so far: button, input, select, date, tag-input, checkbox, radio, switch, card, textarea. node scripts/basecoat-styles.mjs --varying "^\.btn" prints what a style changes for any upstream selector. 14. [Color](https://mono-libs.netlify.app/ui/color.md): @mono-lit/helper paints with Basecoat's token set — --background, --primary, --destructive, --ring … in OKLCH, with a .dark twin for every one — plus seven extension roles Basecoat does not have: success, warning, info, teal, purple, neutral and dark, each with its own -foreground ink. This page lists every value the library ships, per colour preset, in both modes. Pick a palette with a class on (or the theme API): theme-color-one, theme-color-basecoat. Basecoat is the default — it is what :root already declares, so its class is optional. Dark mode is class="dark" on (or , or a .mono-theme wrapper). The role names below are also what a component's color prop accepts (danger paints --destructive). 15. [DOM Type — Light vs Shadow](https://mono-libs.netlify.app/ui/dom-type.md): Every mono-* component ships in two flavors: a light-DOM build and a shadow-DOM build. Most of the time you don't think about it — you just import a component and use it. This page explains the difference in plain terms and which one to pick. 16. [Skills (for AI)](https://mono-libs.netlify.app/ai/skills.md): MONO apps live in separate repositories, so they can't see what the others have learned. MONO Skills fixes that with a knowledge repository you own: per app, its knowledge, reusable skills, technical decisions, and the AI session history — so any app (and any future session) can read what was already figured out. You point an app at that repository with one optional key in mono.config.ts, and everything goes through one CLI: Read before you build When skill is configured, read the app's knowledge first for non-trivial work, and save a session summary after meaningful changes. That's the loop that makes the next session smarter. 17. [Prompting Guide](https://mono-libs.netlify.app/ai/prompting.md): This project is built to be driven by an AI assistant (Claude). How well that goes depends almost entirely on how you prompt it. A vague ask ("add budgeting") sends the assistant guessing; a precise one ("in @src/stores/use-budget.ts, add a transferToProject() action that…") gets you the right change the first time. This page is the short version of how to ask well in this repo. ## LLMs links - [home](https://mono-libs.netlify.app/index.md): A small toolkit that combines a Host/Remote mono-repo pattern with Lit-powered web components — build large, modular Vue front-ends without rebuilding the same generic pieces every time. ### Migration - [Migration: `mono-*` → `@mono-lit/*`](https://mono-libs.netlify.app/migration.md) [read #1]: READ THIS FIRST. The packages were renamed: mono-helper → @mono-lit/helper, mono-utils → @mono-lit/utility (not utils), mono-devextreme → @mono-lit/devextreme, all public on npmjs. Every other page uses the new names; if the code you are working on still imports mono-*, migrate it with this guide before following any other page. ### Mono-Repo - [Getting Started](https://mono-libs.netlify.app/repo/getting-started.md) [read #4]: Mono-repo is a way to build a large-scale, modular front-end on top of the Vue ecosystem. The goal is a maintainable, clean, complex project that takes minimum effort to grow. - [Setup](https://mono-libs.netlify.app/repo/setup.md) [read #5]: The mono ecosystem has two roles: a Host and one or more Remotes (see Getting Started). The Host owns the shared shell; a Remote adds its own pages and plugs into it. A Host is Vue or Nuxt — and the Host and its Remote must have the same framework. A Vue pair (Vue Host + Vue Remote) is joined by mono's own merge; a Nuxt pair (Nuxt Host + Nuxt Remote) by Nuxt's own layer system. Those two choices are the only things that change between setups. The hybrid pairing — possible, but at your own risk Joining a Nuxt Host with a Vue Remote is still possible: mono's compat layer (mono.nuxt()) rewrites the host's Nuxt code so the Remote's Vite build can run it, so nothing blocks the combination. It is outside the standard — the Remote ships as a client-rendered island inside a server-rendered shell, and the seams that compat layer papers over (SSR, layouts, routing, state) remain your risk. Prefer the same-framework pairing; take the hybrid only when you knowingly accept those risks. - [Config](https://mono-libs.netlify.app/repo/config.md) [read #6]: The mono-repo also comes with a config file that handles cookies, JWT state, and deployment. Apps living side by side in one repository? apps[].path reads a sibling folder in place — see Mono-Lith → Config. The config lives in a single file: - [Useful Utils](https://mono-libs.netlify.app/repo/useful-utils.md): Every public API @mono-lit/utility ships, grouped by entry point. Type-only exports are left out — import them from the same entry as the value they belong to. Reach for these before writing custom code (Rule 13 in Template Rules). - [Sync](https://mono-libs.netlify.app/repo/sync.md): The Host and the Remote live in separate repositories. @mono-lit/utility keeps them in step, and both the Mono Host and the Mono Remote (in the mono-lit/templates monorepo) ship with it installed. Sync flows both ways, through GitHub. Neither side talks to the other directly — each one pushes to GitHub, and the other picks the change up on its next sync: A Remote pulls in shell updates from the Host; the Host pulls in each Remote's new modules. Working with the repos side by side? Put every app in one repository as sibling folders and connect them by apps[].path — no sync round-trip, and the host hot-updates when a remote changes. See Mono-Lith. - [Environment](https://mono-libs.netlify.app/repo/env.md) [read #7]: Each app keeps its own environment variables. mono-env is a small wrapper (shipped with @mono-lit/utility) that loads a root env file first, then layers each app's env file from .mono/apps/ on top, and finally runs your real command (vite, vue-tsc, …) with the merged environment. You don't call it directly — it's already wired into every script in the templates, so day to day you just run pnpm dev / pnpm build. That single line means: load .env.dev (root, then every app), then run vite. - [Data Fetching](https://mono-libs.netlify.app/repo/data-fetching.md) [read #8]: To set up fetching, you need three things: a base URL, a token, and the fetch function. Mono ships with all of them. Inside your mono.config.ts, define a fetching object. api is a named map — each entry has a required url, a type ('restful' or 'odata'), and, for odata, its own oDataService. The shared DevExtreme source constructors (dataSource / oDataStore / customStore) are the same for every odata API, so they live once on fetching.source: You then pick which named API a call uses with configBaseUrl: "". It resolves that entry's url (and, for odata, the shared source ctors plus that entry's oDataService), overriding the call's own baseUrl. - [Mock API](https://mono-libs.netlify.app/repo/mock-api.md): Sometimes the backend isn't ready, or you want a demo that anyone can click through without a server behind it. Mono ships a mock backend that lives in the browser's IndexedDB. You declare a schema in mono.config.ts, and every fetching call whose URL matches that schema is answered locally — the request never leaves the page. Because there is no server, there is no port to pick, no CORS to configure, and nothing extra to start. The same code runs in dev and in a static production build, it works offline, and each visitor gets their own private copy of the data. Every app declares its own schema, and they all merge into one store, so a host and its remotes can each bring their own mock data without stepping on each other. - [Provide and Inject](https://mono-libs.netlify.app/repo/provide-inject.md): You can communicate between Remote and Host, or Host and Remote, using the provide-and-inject concept. It can send a string, number, object, or array — and it stays reactive. Both sides share one reactive payload through a common key — either side can provide it and either side can inject it: Let's say this is a Remote that needs to send data — use monoProvide: On the Host side, you can receive the state as long as it shares the same key: This case is kind of unique and rare. For now, the feature already using it is sending menu data from Remote to Host. - [Resolvers](https://mono-libs.netlify.app/repo/resolvers.md) [read #10]: A resolver is a host-side stopgap fix. When a remote app has a bug or isn't ready yet and it breaks the host, the host doesn't wait for the upstream fix — it drops a small file in a resolvers/ folder, wires it in, and deletes it once the remote is fixed upstream. Resolvers live only in the host; the remote stays untouched. - [Template Changelog](https://mono-libs.netlify.app/repo/template-changelog.md) [read #11]: Changes to the starter templates, grouped by change. Each shows the commit and the exact lines to remove (red) / add (green). ### Mono-Lith - [Mono-Lith](https://mono-libs.netlify.app/lith/getting-started.md): A mono-lith is the mono ecosystem with every app in ONE repository, each in its own folder, connected by apps[].path instead of a GitHub clone. The host reads its remotes in place — dev, build, mono prepare and mono sync all look at the same sibling directory — and editing a remote hot-updates the running host. It is the same federation as Mono-Repo: a host compiles its remotes' pages, components, composables and stores as its own, and a remote run standalone borrows the host's shell. What changes is where the other app lives: Every arrow is a line in that app's own mono.config.ts. Each folder keeps carrying its own config, its own package.json and its own pnpm-workspace.yaml — so the folder can still be lifted out into a standalone checkout, where the sibling is gone and mono sync falls back to the url. - [Config: `path` and `url`](https://mono-libs.netlify.app/lith/config.md): apps[].path names the directory an app is read from; apps[].url names the repository mono sync clones it from. An entry needs at least one, path wins whenever its directory exists, and — the rule that makes a lith a lith — path is honoured in every command: dev server, vite build, mono prepare, mono sync, mono env, the Nuxt module and monoNuxtLayers. - [Dev Server & HMR](https://mono-libs.netlify.app/lith/dev-server.md): Running the host reads every sibling live: an edit in apps/esw-project hot-updates the host in the browser, a page added to the sibling appears as a route, and a change to any mono.config.ts restarts the server. Vite does none of that for a directory outside its root on its own — monoLith() is the plugin that adds it, and monoRepo().vite() carries the same hooks. - [CLI & TypeScript](https://mono-libs.netlify.app/lith/cli-typescript.md): The mono commands treat a path app exactly like every other consumer: mono sync has nothing to clone for it, mono env loads its .env. like a clone's, and mono prepare writes its alias into .mono/tsconfig.json — pointing outside the project, which is the one thing TypeScript needs a word about. - [Root runner (Vite+)](https://mono-libs.netlify.app/lith/root-runner.md): The lith's root folder runs the apps; it is not an app itself. One pnpm install gives every sibling the same node_modules, and Vite+ (vp) dispatches dev, build and test to the app you mean — the host by default. ### Mono-Odata - [OData Expression](https://mono-libs.netlify.app/odata/expression.md): OData is a REST protocol with a standard query language built into the URL — you shape the response with $-prefixed options like $select, $filter and $expand instead of asking the backend for a new endpoint. This page explains those options and shows how to send each one through monoFetchOdata, live against a public OData v4 service. - [DataSource](https://mono-libs.netlify.app/odata/datasource.md) [read #9]: A DataSource is a live, reactive handle to a remote OData endpoint that mono's table, select and dropdown-table components consume — it loads rows on demand (one page at a time), exposes paging, filtering and sorting, and re-renders the bound component whenever its data changes. - [OData Types](https://mono-libs.netlify.app/odata/types.md): Every OData service publishes a $metadata document describing all of its entities and their fields. Instead of hand-writing TypeScript interfaces for those entities (tedious, and they drift out of sync with the backend), you generate them from $metadata with odata2ts. There's one catch: odata2ts emits query-object classes (the Q objects used to build type-safe OData queries), not plain field shapes. So the workflow has a small mapping step — turn the raw Q class into a normal type with MonoOdataMapTypes from @mono-lit/utility/runtime. After that the type is safe to use anywhere in the app. The four steps: Configure the service in odata2ts.config.ts. Generate the classes into src/odata/. Map the raw Q* classes to plain types. Use the mapped types across the app. ### Mono-AI - [Prompting Guide](https://mono-libs.netlify.app/ai/prompting.md) [read #17]: This project is built to be driven by an AI assistant (Claude). How well that goes depends almost entirely on how you prompt it. A vague ask ("add budgeting") sends the assistant guessing; a precise one ("in @src/stores/use-budget.ts, add a transferToProject() action that…") gets you the right change the first time. This page is the short version of how to ask well in this repo. - [Template Rules (for AI)](https://mono-libs.netlify.app/ai/template.md) [read #2]: This project is built to be maintained largely by an AI assistant. This page is the contract the AI follows when it writes code in a Host or a Remote. An app is written in one of the ecosystem's template languages — Vue or Nuxt — declared by the type field in its mono.config.ts, and part of these rules depends on which one you're in. The rules are grouped into: Common Rules: General — language-neutral; apply to every app, Host or Remote. Common Rules: Vue / Nuxt — one section per template language. Read only the one matching your app's type. Host template — what the AI may / may not do in the Host. Remote template — what the AI may / may not do in a Remote. Start here and follow the General rules on every change, plus your language's section. New to the repo? Setup shows how a Host (Vue or Nuxt) and a Remote are wired together. This page is the day-to-day contract for writing code inside them. The General rules are ordered as a learning path: understand the machine (1–4) → shared config (5) → naming conventions (6) → environment (7) → where code & data live (8–10) → capabilities: pick a component flavor, fetch, type, and build UI (11–15) → put it together and build a feature (16) → remember what you learned (17). Read top-to-bottom once, then read your language section top-to-bottom once; after that, route by the tables below. - [Reference LLMs (for AI)](https://mono-libs.netlify.app/ai/ref-llms.md) [read #3]: When you (an AI assistant) need authoritative API details for any library in this stack, read that project's official llms.txt first instead of relying on memory. An llms.txt is the project's documentation flattened into a single machine-readable markdown file, so it is the most reliable source for current APIs, options, and examples. How to use Fetch the relevant URL below and ground your answer in it before writing or changing code. Prefer the llms.txt reference over assumptions, and base the behavior you rely on on what the docs actually say. - [Skills (for AI)](https://mono-libs.netlify.app/ai/skills.md) [read #16]: MONO apps live in separate repositories, so they can't see what the others have learned. MONO Skills fixes that with a knowledge repository you own: per app, its knowledge, reusable skills, technical decisions, and the AI session history — so any app (and any future session) can read what was already figured out. You point an app at that repository with one optional key in mono.config.ts, and everything goes through one CLI: Read before you build When skill is configured, read the app's knowledge first for non-trivial work, and save a session summary after meaningful changes. That's the loop that makes the next session smarter. ### Mono-Addons - [Chart.js](https://mono-libs.netlify.app/addons/chart.md): Charts built on Chart.js. A controlMonoChart(...) controller projects your rows into chart data — the same controller-plus-.prop shape as controlMonoTable — and the elements own the canvas. Install Chart.js is an optional peer — it isn't bundled, so nothing pays for it unless you chart something. The version is pinned exact. If it's missing, the element renders an inline message naming the package and the command rather than failing silently. - [Rich Text Editor](https://mono-libs.netlify.app/addons/rich-text-editor.md): A WYSIWYG field built on SunEditor v3. is a mono form field — label, helper and validation chrome, size / color / variant, monoForm binding — whose input is a SunEditor instance. The value is HTML. Install SunEditor is an optional peer — it isn't bundled, so nothing pays for it unless a page renders the editor. If it's missing, the element renders an inline message naming the package and the command (and emits error) rather than failing silently. The editor's own stylesheet (suneditor/css/editor) is loaded by the element the first time one renders; set load-css="false" if your app ships it itself. Plain JS, not a wrapper There is no published web-component or Vue build of SunEditor — the "Web Components" example on suneditor.com is a fifteen-line custom element around the core package, and suneditor-vue is an empty repository. This element wraps the core suneditor package the same way that example does (a light-DOM mount, created on connect and destroyed on disconnect) and adds what it lacks: lazy loading, v-model and monoForm, the mono chrome and theming. - [Export Table](https://mono-libs.netlify.app/addons/table-export.md): Turn a controlMonoTable into a document with table.export(). The report layout is authored as a Markdown file — Handlebars fills it from your data, Jexl evaluates inline expressions, and the result renders to Markdown (for preview) or to a real .xlsx workbook with styles, formulas and merged cells. Data stays in TypeScript, presentation stays in the template. The template sees exactly what you put in data — nothing is injected behind your back. A report that needs the grid's rows asks for them, visibly, at the call site. Working from a remote DataSource? Pass it straight in and skip the fetch — From a DataSource: The engine is loaded on first use via a dynamic import, and its heavy dependencies are resolved from your app rather than bundled — so importing controlMonoTable costs nothing if you never export, and @mono-lit/helper stays importable on the server. Install Both are optional peers — they aren't bundled, so nothing pays for them unless you export something. handlebars is the template engine, so every report needs it — Markdown included. exceljs is only reached when an .xlsx is actually rendered. If either is missing, the export throws an error naming the package and the command rather than failing obscurely. Going the other way — reading an edited sheet back into the grid — is Import Table. - [Import Table](https://mono-libs.netlify.app/addons/table-import.md): Read an edited spreadsheet back into the grid with table.import(). Each sheet row is paired with a table row by composite key, the declared columns are parsed and compared, and the differences land in the grid's staged changes — shown optimistically, reviewable, and written to your data only when you call saveChanges(). Nothing is touched until then, and discardChanges() undoes the whole import. The parser is loaded on first use, so importing costs nothing until you do it. Install for .xlsx Reading a real workbook uses the optional exceljs peer — the same one Export Table needs. Pasted text needs nothing. - [Notivue](https://mono-libs.netlify.app/addons/notivue.md): Toast notifications for the template apps, built on notivue. You don't call notivue directly — you push through notif() (from useMonoUtility) and render with the MonoNotivue component. It's an optional dependency: skip it if an app doesn't need notifications. - [Tooltip](https://mono-libs.netlify.app/addons/tooltip.md): Tooltips for any element: plain HTML, light components, and shadow hosts. Positioning comes from Floating UI. Turn the addon on once, then write the attribute. No component needs any tooltip code: For tooltips set up from code (content computed per element, a ref, manual show/hide), use controlMonoTooltip. Install @floating-ui/dom and @floating-ui/core are optional peers. They aren't bundled, and you install them yourself: They load the first time a tooltip opens, not on import, so registering tooltips costs nothing until someone hovers. If the packages are missing, the tooltip doesn't open, and the console names the packages and the install command. - [Skeleton](https://mono-libs.netlify.app/addons/skeleton.md): Loading skeletons for every mono component, measured from the component's real DOM — no hand-drawn placeholder per screen. Rendering comes from phantom-ui, a structure-aware shimmer web component. There is nothing to call and nothing to import in your code: every element already has a pending prop. Install the peer and it is on. Install @aejkatappaja/phantom-ui is an optional peer. It is not bundled; you install it: Nuxt (@mono-lit/helper/nuxt): nothing else. The module sees the package, loads it in a client plugin before the app mounts, and tells mono whether the app renders on the server. Vue: one side-effect import in main.ts, before app.mount(): mono activates the moment the element is defined — however it got there. Without the package, pending degrades to the CSS-only block skeleton for manual values; the automatic behaviour stays off. - [Yup](https://mono-libs.netlify.app/addons/yup.md): Schema-based form validation, built on Yup. You don't call Yup's .validate() yourself — you build a schema, keep an error object, and run it through the validation helpers from useMonoUtility (validateAllSchema, validateSchema, validateAllSchemaCheck, clearSchemaValidation). Yup is an optional dependency: skip it if an app has no forms to validate. ### Mono-UI - [Getting started](https://mono-libs.netlify.app/ui/getting-started.md) [read #12]: Mono UI is a small set of Lit-powered web components — , , , etc. They're standard custom elements, so they work in Vue, React, Svelte, plain HTML, or anywhere custom elements run. - [Theme](https://mono-libs.netlify.app/ui/theme.md) [read #13]: @mono-lit/helper's look is Basecoat UI's. Three independent axes, all plain CSS classes on a root element, all crossing the shadow boundary through inherited custom properties: They are orthogonal: Nova metrics with the ONE palette in dark mode is a valid combination. Components don't change — once a class is active, every mono- component (light and shadow build alike) picks it up. ONE (the EJI ONE design system) is the default: index.css applies it at :root, nothing to import or class. The other eight flavors are Basecoat's eight styles, mirrored 1:1: a style never touches tokens or fonts, only how each component is sized and shaped. Each ships as its own sheet — a vega base that resets what ONE sets, then the style's own overrides for the knobs ported components read (--mono-control-height-, --mono-button--radius, --mono-input-outline-*, …). Ported so far: button, input, select, date, tag-input, checkbox, radio, switch, card, textarea. node scripts/basecoat-styles.mjs --varying "^\.btn" prints what a style changes for any upstream selector. - [Color](https://mono-libs.netlify.app/ui/color.md) [read #14]: @mono-lit/helper paints with Basecoat's token set — --background, --primary, --destructive, --ring … in OKLCH, with a .dark twin for every one — plus seven extension roles Basecoat does not have: success, warning, info, teal, purple, neutral and dark, each with its own -foreground ink. This page lists every value the library ships, per colour preset, in both modes. Pick a palette with a class on (or the theme API): theme-color-one, theme-color-basecoat. Basecoat is the default — it is what :root already declares, so its class is optional. Dark mode is class="dark" on (or , or a .mono-theme wrapper). The role names below are also what a component's color prop accepts (danger paints --destructive). - [DOM Type — Light vs Shadow](https://mono-libs.netlify.app/ui/dom-type.md) [read #15]: Every mono-* component ships in two flavors: a light-DOM build and a shadow-DOM build. Most of the time you don't think about it — you just import a component and use it. This page explains the difference in plain terms and which one to pick. - [Global Defaults](https://mono-libs.netlify.app/ui/global-defaults.md): createMonoUI changes the default props of any mono component for the whole app. For example, it can make every button xs or every input clearable. It applies to the light (mono-) and the shadow (mono-shadow-) build alike. - [Form](https://mono-libs.netlify.app/ui/form.md): A headless form controller — there is no mono-form element. You author a native
and place controls wherever you like; controlMonoForm owns the value, the validation, the cross-field reactions and the props pushed onto each control. Controls opt in with two props: :control-form (the controller) and key-form (which field it is). That's the whole binding — no :model-value, no @change, because the form owns the value and writes it into the control. - [Checkbox](https://mono-libs.netlify.app/ui/checkbox.md): A control for boolean / multi-state selection. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .input[type="checkbox"] (see Theme): a 16px box (4/9 of the control step, so it stays paired with the input beside it) with a 1px --input border, rounded-[4px], an xs shadow, the 3px --ring focus ring, and a --primary fill with Basecoat's own --check-icon when checked; the label is Basecoat's .label (text-sm font-medium leading-snug, gap-2). Dark mode is built in. - [Radio](https://mono-libs.netlify.app/ui/radio.md): A single radio option used standalone or as part of a group sharing the same model-value. Sizes, color variants, an optional sublabel, and slot-overridable label and sublabel text. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .input[type="radio"] (see Theme): a 16px circle (4/9 of the control step, so it stays paired with the checkbox and input beside it) with a 1px --input border, the 3px --ring focus ring, and — when selected — a --primary fill carrying a --primary-foreground dot at half its width. The label is Basecoat's .label (text-sm font-medium leading-snug, gap-2). Dark mode is built in. - [Switch](https://mono-libs.netlify.app/ui/switch.md): A boolean toggle with sizes, color variants, optional loading state, and slot-overridable label and sublabel text. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .input[type="checkbox"][role="switch"] (see Theme): a 32 × 18.4px pill with a 1px transparent border and an xs shadow, --input when off and --primary when on, carrying a 16px --background thumb that travels calc(100% - 2px). That thumb is exactly the checkbox box (the control step × 4/9), so a switch, a checkbox and a radio in one column share one scale — and every size derives from it. Dark mode is built in (the thumb flips to --foreground when off, --primary-foreground when on). - [Input](https://mono-libs.netlify.app/ui/input.md): A single-line text input with sizes, color and visual variants, validation states, prefix/suffix slots and a clearable button. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .field / .input-group / .label (see Theme): a 1px --input border, an xs shadow, a 3px --ring focus ring, --muted-foreground placeholder and affixes, and text-sm label / message text. Dark mode is built in. - [Date](https://mono-libs.netlify.app/ui/date.md): A rich date / datetime / time picker built on flatpickr. Set type to date (default), datetime, or time. The common flatpickr options are exposed as named props, and the options prop passes through any other flatpickr option — so the picker is completely featured. model-value is the formatted string (flatpickr's dateStr); the change event also carries the raw Date[] in detail.dates. It shares mono-input's look: sizes, colors, variants, validation, clearable, cssClass, and the width/height sizing props. The field is Basecoat's .input-group with a leading icon (see Theme and Input); the calendar is flatpickr restyled in Basecoat's popover idiom — a --popover surface with a 1px --foreground/10 ring and an md shadow, --accent for hover / today / the range middle, --primary (or the color prop's role token) for the selected day. Dark mode is built in, calendar included. - [Textarea](https://mono-libs.netlify.app/ui/textarea.md): A multi-line text input with sizes, color and visual variants, validation states, slot-overridable label and helper, JS-driven auto-resize, and an optional character counter. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .textarea inside a .field (see Theme): a rounded-md box with a 1px --input border, an xs shadow, px-2.5 py-2 and text-sm, with the 3px --ring focus ring the input uses. Where upstream pins a flat min-h-16, mono derives the height from the control step — T + (rows - 1) x line — so a textarea starts its first line on the same baseline as an input beside it and reads as "one input tall, plus N-1 lines". Dark mode is built in. - [Select](https://mono-libs.netlify.app/ui/select.md): A dropdown picker built on a custom button + listbox UI. Sizes, color and visual variants, validation states, slot-overridable label/helper/prefix/suffix, and a clearable trigger. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .select / .combobox (see Theme): the trigger is the same box as input (1px --input border, xs shadow, 3px --ring focus ring), the panel is a --popover surface with a 1px --foreground/10 ring and an md shadow, options are text-sm rows that highlight on --muted and mark the selected one with a check at the end. Dark mode is built in. - [File upload](https://mono-libs.netlify.app/ui/file-upload.md): A drag-and-drop file uploader with previews, validation states and a built-in file list. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .empty (the dropzone) and .item (the file rows) inside a .field (see Theme): a dashed rounded-lg drop target padded p-12 around a muted icon plate, a text-lg prompt and a text-sm/relaxed hint, over outlined rounded-md rows with a ghost icon button to remove each one. Dark mode is built in. Upstream's empty state is genuinely large, so the default dropzone is taller than the pre-Basecoat one — variant="compact" is the short one, and --mono-file-upload-dropzone-padding moves it in a line. - [Tag input](https://mono-libs.netlify.app/ui/tag-input.md): A multi-value tag entry with optional autocomplete suggestions, validation states, min / max selection limits, and slot-overridable label/helper. Tags are added with Enter, , or Tab, and removed with × or Backspace on an empty input. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's multi-select combobox (see Theme): the field is .combobox-chips (the input box, px-1.5 py-1.5, chips wrapping inside it), each tag a .combobox-chip (a muted text-xs pill-less chip with a fading ✕), the suggestion list the select's panel. Dark mode is built in. - [Button](https://mono-libs.netlify.app/ui/button.md): A clickable button or link element. Toggle Vue / CSS to switch the live demo and source together. - [Button dropdown](https://mono-libs.netlify.app/ui/button-dropdown.md): A row of actions that stays flat while it's short and collapses behind a ⋮ menu once it isn't. — button plus dropdown in one element. The menu is Basecoat's .dropdown-menu (see Theme): a bg-popover panel with a ring-1 ring-foreground/10 edge, rounded-md, shadow-md and p-1, holding rounded-sm rows that wash bg-accent under the pointer. Dark mode is built in. buttons and trigger are objects, so bind them with .prop — a plain attribute would only carry a stringified value. - [Chip](https://mono-libs.netlify.app/ui/chip.md): A small status or label chip. Slot any content inside — text, count, icon. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .badge (see Theme): a h-5 rounded-4xl pill with px-2, text-xs and font-medium, in three variants — soft (the tonal bg-c/10 text-c badge, and mono's default), solid (bg-c text-c-foreground) and outline. Dark mode is built in: the tonal fill deepens through --mono-mode-tint. - [Card](https://mono-libs.netlify.app/ui/card.md): A surface for grouping related content with optional header and footer actions. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .card (see Theme): a --card surface at rounded-xl, edged with a 1px --foreground/10 ring rather than a border (drawn inside the box, so no clipping ancestor can eat it), an xs shadow and text-sm type, with gap-6 py-6 and every region inset px-6 so a media band can run full-bleed between them. Dark mode is built in. - [Alert](https://mono-libs.netlify.app/ui/alert.md): A callout for a short message: an optional icon, a title and a subtitle, with no header region. Alerts are meant to stack, and the optional ✕ hides one. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .alert (see Theme): a card surface edged with --border, rounded-lg, px-4 py-3 and text-sm, with the icon in its own column. Every theme style has its own alert (lyra and mira are compact, luma and rhea are rounded-2xl, sera adds a coloured bar on the start edge), and dark mode comes from the colour tokens. - [Table](https://mono-libs.netlify.app/ui/table.md): A headless, native- helper. controlMonoTable wraps a DataSource or a plain array and small controls (, , , …) drive it — bind each with :control-table. Declare every control's props in the controller's props block and each element needs only :control-table.prop="table"; props.th holds one entry per column, which you loop yourself from a state ref. Grid-as-a-document lives in Export Table. - [Dropdown table](https://mono-libs.netlify.app/ui/dropdown-table.md): A dropdown that opens a native
for picking row(s). controlMonoDataDropdown wraps controlMonoTable (exposed as dd.grid), so the panel's table controls bind with :control-table.prop="dd.grid". Bind the field with :control-data-dropdown.prop="dd". - [Filter builder](https://mono-libs.netlify.app/ui/filter.md): A universal, inline OData filter builder. controlMonoFilterBuilder owns the filter; renders it as nested rule rows and writes edits straight back. - [Accordion](https://mono-libs.netlify.app/ui/accordion.md): A single expand/collapse unit with header (icon + title + optional subtitle + optional trailing actions) and a body. Compose multiple together for a typical FAQ or settings group. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .accordion (see Theme): a p-4 row with a text-sm font-medium title and a size-4 chevron that rotates when the item opens, over a text-sm body. In a group the items flatten into one flush list with a hairline between them, exactly as upstream renders .accordion > details; on its own an item keeps this component's frame (border, radius, shadow-xs), which upstream has no equivalent for. Dark mode is built in. - [Tabs](https://mono-libs.netlify.app/ui/tabs.md): A tab strip with sizes, color variants, three visual styles (underline / pill / ghost), optional count badges, optional SVG icons via slot, and per-item disabled state. Content panes live outside the strip — drive them off the bound model-value. Toggle Vue / CSS to switch the live demo and source together. The strip IS Basecoat's [role="tablist"] (see Theme). pill is upstream's default — a bg-muted track, rounded-lg p-[3px] h-9, with the selected tab lifted onto bg-background shadow-sm — and underline is its [data-variant="line"]: a bare track where the selected tab is marked by a 2px bar. Tabs are text-sm font-medium, inked foreground/60 until hovered or selected. Dark mode is built in. - [Drawer](https://mono-libs.netlify.app/ui/drawer.md): A side-anchored overlay panel that slides in from any of the four edges. Use it for detail views, filter panels, or contextual forms that don't deserve a full route. Bind open (or use the model-value alias) to control visibility, listen to toggle for state changes, and pick the slide direction with position. Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .drawer (see Theme): a bg-popover sheet pinned to one viewport edge that rounds and borders only the edge facing the content, with p-4 header / body / footer regions, over a bg-black/10 backdrop-blur-xs scrim, sliding on duration-500 ease-[cubic-bezier(0.32,0.72,0,1)]. Two things changed with the port: the tinted gradient header bar and the tinted footer are gone (--mono-drawer-header-bg / -footer-bg and the matching -border / -border-width put them back), and the footer is now a stacked column, upstream's mobile-sheet idiom, which --mono-drawer-footer-direction: row reverses. The default ONE flavor sets that row back, so the pre-port action row survives; the tinted bars are restored nowhere, because dropping them is a decision of the port rather than a flavor's. Dark mode is built in. - [Dropdown](https://mono-libs.netlify.app/ui/dropdown.md): A flexible floating overlay that pairs any activator (slot="main") with any body content (slot="body"). Twelve placements (top/bottom/left/right × bare, -start, -end), three trigger modes (click / hover / manual), ten theme colours, sm/md/lg sizes. The panel auto-flips to the opposite side when the requested side runs out of viewport space and shifts along the cross-axis to stay on-screen. Outside-click and Escape both close (configurable). Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .popover + [data-popover] (see Theme): a bg-popover panel with a ring-1 ring-foreground/10 edge, rounded-md, shadow-md, p-4, gap-4 and text-sm, opening with upstream's scale-and-slide. Dark mode is built in. - [Modal](https://mono-libs.netlify.app/ui/modal.md): A centered overlay dialog with header / title / subtitle / body / footer slots, six content sizes, ten theme colours, and a modelValue binding contract that matches drawer / popover / toast. Renders into a body portal to escape transformed ancestors and stays fully invisible while closed (no shadow halo bleed). Toggle Vue / CSS to switch the live demo and source together. The look is Basecoat's .dialog (see Theme): one padded box — the panel carries p-6 and gap-6 and the header / body / footer are unpadded, unbordered, untinted regions inside it — on bg-popover with a ring-1 ring-foreground/10 edge and rounded-xl, over a bg-black/10 backdrop-blur-xs scrim. The tinted gradient header bar, the tinted footer and both divider rules are gone; --mono-modal-header-bg / -footer-bg (+ the matching -border / -border-width / -pad) put them back. Dark mode is built in. - [Nav](https://mono-libs.netlify.app/ui/nav.md): A sticky top app bar built around three named slots — start, default (center), and end — plus an optional extension second row. Three densities (compact / comfortable / default), three variants (flat / elevated / outlined), and eleven colors (ten palette roles + surface) — or any CSS color, e.g. color="#7c3aed". Writes its resolved height to --mono-nav-height on :root so layout wrappers can pad themselves without prop drilling. Toggle Vue / CSS to switch the live demo and source together. Basecoat ships no nav component — basecoat-css@1.0.2 has dialog, drawer, sidebar, tabs and the rest, but no header bar — so this is an extension. What it does ship is a wide rectangular surface with an edge and an elevation, and that is exactly what a top bar is: the nav's chrome tracks .card value for value — radius, ring, elevation, gutter and type — so every flavor's nav delta is its card delta. sera's bar is square, roomy and shadow-sm; maia's is rounded-2xl and flat; luma's is the rounded-4xl pill with shadow-md; lyra's is square, tight and text-xs. Put a nav above a card and the two edges line up. The bar's own ladder — three densities, three variants, the extension row — is mono's, and .sidebar nav supplies the chrome ink (--sidebar for the fill, the bar ink at 70% for muted copy, --sidebar-border for the extension hairline). A coloured bar paints in the role and inks in that role's -foreground, the way .btn[data-variant="primary"] does. The two-stop brand gradients, the --*-rgb triples and the hand-rolled contrast maths are gone, and dark mode comes free. A full-bleed bar wants no corners The bar inherits the card's radius and the card's inset ring, which is right inside a page frame and wrong flush against the viewport. At the very top of a page, set --mono-nav-radius: 0 and --mono-nav-ring-width: 0. - [Sidebar](https://mono-libs.netlify.app/ui/sidebar.md): A Vuetify-style navigation drawer with four modes — permanent (always visible, pushes content), temporary (overlay drawer with scrim + Escape), rail (mini collapsed, optional expandOnHover), and auto (responsive: permanent on ≥ 768 px viewports, temporary below). Anchors left or right, three densities, three variants, eleven colors (ten theme palette slots + surface) — or any CSS color, e.g. color="#7c3aed". Three named slots — header, default (body), footer — designed to host a future in the body. Writes its width to --mono-sidebar-{left,right}-width on :root so layout wrappers can pad themselves. Demos use the contained prop to position absolutely inside a scoped preview pane. Toggle Vue / CSS to switch the live demo and source together. - [Menu](https://mono-libs.netlify.app/ui/menu.md): A recursive nav list — items, collapsible groups, dividers, and subheaders — driven by a single items array. Single or multi-select, three densities, eleven colors (ten theme palette slots + surface) — or any CSS color, e.g. color="#7c3aed", two style variants (nav for the dashboard-pill look, plain for a softer subtle fill), badges with status colors, and Iconify icons via item.icon (e.g. i-mdi-view-dashboard). Designed to fit straight into 's body slot to compose a Vuetify-style app shell. Toggle Vue / CSS to switch the live demo and source together. - [Breadcrumb](https://mono-libs.netlify.app/ui/breadcrumb.md): A wayfinding component for page hierarchies — pair it with a page header above the title and subtitle. Items render as when they include href, as