LoomiUI
Modern web components for every framework. LoomiUI is a modern, open-source UI component library built on native Web Components and web standards. It provides a collection of simple, accessible, and customizable components that work across frameworks or with no framework at all, helping developers build beautiful and consistent web interfaces faster.
📖 Visit loomiui.com for complete documentation, live previews, and API details for every component.
<loomi-button color="error" outline radius="full" icon="trash">Delete</loomi-button>
Why LoomiUI
- Use familiar HTML. Every LoomiUI component is a browser custom element. You can use the same tags in React, Vue, Angular, Svelte, a static site, or plain HTML. A framework wrapper is not required.
- Change the theme from CSS. Component colors come from CSS custom properties whose
names start with
--loomi-. Override those properties in your application CSS to update every component. Your application does not need Tailwind or an extra theme build step. - Ship no Tailwind runtime. LoomiUI compiles Tailwind while building each package and includes the resulting CSS inside the component. Applications that use LoomiUI do not load or run Tailwind in the browser.
- Install only what your application uses. LoomiUI provides 78 components as separate npm packages. You can install one component, a category of components, or the complete library.
- Submit values with native forms. LoomiUI form controls use
ElementInternalsto participate in a normal HTML<form>. Their values are included when the form is submitted, just like values from built-in inputs. - Give coding tools accurate component documentation. The included
@loomidev/mcp-serverlets compatible tools read the real usage guides and attribute tables. See MCP documentation server.
Project status: LoomiUI has not reached version 1.0. Each package has its own
0.xversion. A minor update may contain breaking changes until that package reaches 1.0.
Quick start
To try one component without installing a package, load Lit and the LoomiUI component from a CDN:
<script type="importmap">
{ "imports": { "lit": "https://esm.sh/[email protected]", "lit/": "https://esm.sh/[email protected]/" } }
</script>
<script type="module" src="https://esm.sh/@loomidev/button"></script>
<loomi-button>Hello LoomiUI</loomi-button>
For an application project, install the component and Lit from npm:
npm install @loomidev/button lit
import "@loomidev/button"; // registers <loomi-button>
<loomi-button color="primary" icon="check">Save changes</loomi-button>
Two ways to install
Install everything
npm install @loomidev/components lit
import "@loomidev/components"; // registers every LoomiUI element
Install just what you need
Each component is available as its own package. Its shared LoomiUI dependencies, such as
@loomidev/core and @loomidev/theme, are installed automatically.
npm install @loomidev/select @loomidev/datepicker lit
import "@loomidev/select";
import "@loomidev/datepicker";
You can install a package that contains a whole category. Available category packages
include @loomidev/forms, @loomidev/content, and
@loomidev/navigation. If you install @loomidev/components, you
can still import only one component with import "@loomidev/components/button".
Install
litalongside your LoomiUI packages. This allows the application and all LoomiUI components to share one installed version of Lit.
Theming
Set LoomiUI color variables in your application CSS. You do not need Tailwind or another build step:
:root {
--loomi-primary-600: #16a34a; /* every primary-colored component turns green */
--loomi-primary-700: #15803d; /* its hover shade */
}
Every component reads its colors from CSS custom properties that follow the
--loomi-<color>-<shade> naming pattern. These properties pass from your page into each
component's Shadow DOM. Defining them on :root therefore updates every LoomiUI
component on the page.
Supported colors: primary, secondary, success, error, warning, and gray.
Each color includes shades from 50 through 950, such as --loomi-warning-500.
For technical details about public theme variables and internal component colors, see
@loomidev/core's README.
Internationalization
@loomidev/core provides translations for text built into the components. This includes
placeholders, validation messages, accessible labels, pagination text, and month and
weekday names in the datepicker. Set one locale for the whole application before
rendering components. You can also set the locale attribute on one component when it
needs a different language.
import { setLoomiLocale, defineLoomiTranslations } from "@loomidev/core";
import "@loomidev/components";
await setLoomiLocale("es");
defineLoomiTranslations("ak", {
datepicker: { placeholder: "Paw da a wobɛpaw" },
filepicker: { placeholderLine1: "Paw fael anaa twe bra ha" },
});
<loomi-datepicker locale="fr"></loomi-datepicker>
<loomi-filepicker locale="pt_BR"></loomi-filepicker>
Only English ships with the components; other included locales are fetched on first use.
setLoomiLocale() switches once the locale has loaded, and open components re-render. A
locale attribute naming a locale that isn't loaded yet renders in English until it
arrives, unless you preload it with await loadLoomiLocale("fr").
Included locales are en, ar, de, es, fr, it, ml, pt_BR, tr, and
zh_CN. If a component provides an attribute for specific text, that attribute overrides
the translated default.
Components
Each package README includes installation instructions, examples, and an attribute table. You can also browse the rendered documentation and live previews at loomiui.com.
Using LoomiUI with a framework
LoomiUI components use the browser's custom elements standard, so they work with React,
Vue, Angular, and other frameworks. Install the package, import it once to register the
component, then use its <loomi-*> tag in your template.
Text values and simple attributes such as color, size, and disabled work like normal
HTML attributes. Arrays, objects, and functions are JavaScript values, so they must be
passed to the component as properties. For example, <loomi-select> receives its options
through the .data property.
React 19 can pass these JavaScript values to custom-element properties through JSX. React 18 and earlier cannot do this directly. In those versions, create a ref for the LoomiUI element and set the property after the component mounts:
import { useEffect, useRef } from "react";
import "@loomidev/select";
function CountryPicker() {
const ref = useRef(null);
useEffect(() => {
ref.current.data = [
{ label: "Canada", value: "ca" },
{ label: "India", value: "in" },
];
}, []);
return <loomi-select ref={ref} placeholder="Country" />;
}
For JSX autocomplete and property checking in a TypeScript React project, install the
types package and include it in your tsconfig.json:
npm install --save-dev @loomidev/react-types
{
"compilerOptions": {
"types": ["@loomidev/react-types"]
}
}
@loomidev/react-types supports React 18 and React 19 without adding runtime wrappers.
It improves TypeScript checking, but React 18 still needs refs for arrays, objects,
functions, and custom DOM events.
Vue and Angular can pass JavaScript values directly through their normal property binding syntax. You do not need a ref:
<loomi-select :data="countries" placeholder="Country" />
<loomi-select [data]="countries" placeholder="Country"></loomi-select>
Server-side rendering
Every LoomiUI component renders on the server to Declarative Shadow DOM, so a page can contain real, styled markup before any JavaScript runs. This works from any stack that can run Node during the request or the build - Astro, Nuxt, Next.js, or a static site generator - and the resulting HTML is plain markup that Rails, Laravel or Django can serve just as well.
import { render } from "@lit-labs/ssr";
import { collectResultSync } from "@lit-labs/ssr/lib/render-result.js";
import { html } from "lit";
import "@loomidev/components/button";
const markup = collectResultSync(render(html`<loomi-button>Save</loomi-button>`));
// <loomi-button><template shadowrootmode="open"><style>…</style><button …>…</button></template></loomi-button>
Browsers parse that <template shadowrootmode> into a real shadow root during HTML
parsing, so the component is visible and styled with no JavaScript at all. To make it
interactive, load the component modules as usual; to have Lit adopt the server-rendered
DOM rather than replace it, import Lit's hydration support before any component:
import "@lit-labs/ssr-client/lit-element-hydrate-support.js";
import "@loomidev/components/button";
One limitation to know. A handful of components inspect their light-DOM children to
decide what to render - <loomi-select> reading <option> elements, <loomi-tabs>
finding its <loomi-tab> children, <loomi-table> reading a <template slot="row">.
The server has no light DOM to inspect, so anything derived from those children is absent
from the server HTML and fills in at hydration. Nothing throws, and the rest of the
component still renders.
Passing the same data through a property avoids this, because properties are readable on
the server. <loomi-select .data=${options} selected-value="gh"> server-renders its
trigger showing the selected label, where the <option> form renders the placeholder
until hydration. Note that this affects the closed control only - a select's option
list is not in the server HTML either way, since the panel is rendered only while open.
CI runs two checks over this. pnpm check:ssr renders all 103 components in Node and
fails if any throws or emits no shadow root; pnpm check:hydration then loads that markup
in a browser and fails if the client replaces the server-rendered DOM instead of adopting
it - which is what would show up to a user as a flash of re-rendered content.
Browser support
LoomiUI supports recent versions of Chrome, Edge, Firefox, and Safari. LoomiUI form
controls use the browser's ElementInternals API to submit values through native forms.
This form behavior requires Safari 16.4 or later. Older Safari versions can display
the components, but they cannot include LoomiUI control values when submitting a native
<form>.
MCP documentation server
@loomidev/mcp-server is a
Model Context Protocol (MCP) server. It gives compatible
coding tools access to LoomiUI documentation, including component attributes and usage
examples.
npx @loomidev/mcp-server
See the package README for instructions on connecting an MCP client. After the connection is configured, the client can look up accurate attribute tables and usage examples.
Contributing
See CONTRIBUTING.md to learn about the project structure, theming implementation, component development process, build steps, and npm release process.
For task-focused references, see the contributor workflow, operations runbook, maintainer guide, and release communication templates.