Getting Started
One model, four authoring modes
The component contract comes first
A Looma definition declares the component tag, native light-DOM root, inputs, methods, events, slots, and controller behavior. HTML Next is the canonical form. Vue projects that same contract into its own lifecycle; it does not invent a competing API.
- 1Author declaratively
Semantic content exists before JavaScript and remains visible to the platform.
- 2Lower to a native root
The runtime attaches the declared state and controller without a shadow-root bridge.
- 3Project into a host
Framework adapters own lifecycle integration while preserving the same public contract.
The same component, expressed for your host
HTML Next is selected by default. Vue shows the same definition through@threadlabs/looma/vue; the live result exercises the same Looma definition.
<script type="module">
import "@threadlabs/looma";
</script>
<ui-stack gap="m">
<h2>Account</h2>
<ui-button variant="solid">Save</ui-button>
</ui-stack>
Install Looma
Looma is published on npm for Vue 3 and direct declarative HTML use. It is pre-1.0, so the surface can still change between minor versions. React support is in development.
Use Node 20 or newer:
pnpm add @threadlabs/looma
Vue and Tiptap are optional to Looma as a whole. The general Vue adapters need only Vue 3.5 or newer and do not load the editor graph:
pnpm add @threadlabs/looma vue@^3.5.0
The Vue editor entry adds Tiptap 2. Pin Tiptap's official Vue lifecycle package to the supported 2.x line:
pnpm add @threadlabs/looma vue@^3.5.0 @tiptap/vue-3@^2.11.5
Looma ships the concrete Tiptap extensions used by its editor preset inside the
editor subpath. You do not need to enumerate those packages yourself.
@tiptap/vue-3 is explicit because the turnkey LoomaEditor uses Tiptap's
official Vue lifecycle. Applications that use /editor without Vue should
install a compatible @tiptap/core 2.x instead.
The root package and @threadlabs/looma/vue work without Tiptap. Looma's /editor and
/vue/editor entries are Tiptap-backed by design.
Import the tokens and the components
Import the design tokens and one theme once in the browser entry for your application. For HTML pages, import the package to register every component; each component carries its own scoped styles.
import "@threadlabs/looma/tokens.css";
import "@threadlabs/looma/theme-light.css";
import "@threadlabs/looma";
Vue applications import the Vue components and their stylesheet instead:
import "@threadlabs/looma/tokens.css";
import "@threadlabs/looma/theme-light.css";
import "@threadlabs/looma/vue.css";
Choose only one Looma theme file unless your application supplies its own semantic-token values. Importing the public modules during server rendering is supported; document lowering and controller behavior wait for a browser.
How components load
HTML Next defines two ways to load components, and they build the same components:
- Installed package (above). Your bundler imports Looma's entry points, which register the component definitions ahead of time. This is how Looma is used today.
- No build. A page loads HTML Next's browser entry with a
<script type="module">and links each component's HTML with<link rel="component" href="…/@threadlabs/looma/components/ui-button/ui-button.html">; definitions load on demand.
Render with a framework adapter
The mode control above changes the syntax, not the component model. The Vue components are converted from the same definitions: each renders the component's native root with Vue, with the same props, events, slots, methods, and behavior, and no HTML Next runtime.
<script setup lang="ts">
import { Button, Stack } from "@threadlabs/looma/vue";
</script>
<template>
<Stack gap="m">
<h1>Account</h1>
<Button variant="solid">Save</Button>
</Stack>
</template>
For a complete Vue editor, pass content and host integration callbacks to Looma:
<script setup lang="ts">
import { ref } from "vue";
import {
LoomaEditor,
type LoomaMentionProvider,
} from "@threadlabs/looma/vue/editor";
const content = ref({ type: "doc", content: [] });
const findPeople: LoomaMentionProvider = async (query, { limit }) => {
const response = await fetch(
`/api/people?q=${encodeURIComponent(query)}&limit=${limit}`,
);
return (await response.json()).people;
};
</script>
<template>
<LoomaEditor
v-model="content"
:mention-provider="findPeople"
:mention-limit="8"
:upload-image="async file => ({ url: await upload(file), alt: file.name })"
/>
</template>
To add only Looma table behavior to an existing Tiptap editor:
import { Editor } from "@tiptap/core";
import { LoomaTableKit } from "@threadlabs/looma/editor/extensions";
const editor = new Editor({ extensions: [LoomaTableKit] });
Know the boundary
@threadlabs/loomais the complete R1 public package; supported capabilities live at its explicit subpaths.- Every published component contract lowers to its declared native light-DOM root. No custom-element registry or shadow-root implementation is part of the public model.
- React support is in development; the package has no React export.
LoomaEditorowns its Tiptap lifecycle, formatting controls, slash commands, bounded mention suggestions, focus behavior, image insertion, and table editing.- Hosts own persistence, the authorized people-directory query, upload transport, collaboration, presence, workspace/page concepts, and app-specific commands.
Read the Release 1 support and limitations before adopting Looma, then use the component pages for exact markup and API contracts.