---
title: "LemonadeJS Studio Component Catalog"
description: "LemonadeJS Studio, a 43-block contract-verified catalog: Modal as the platform primitive, with a contract, a proof and generated docs behind every block."
source: https://lemonadejs.com/docs/studio/
---

<link rel="stylesheet" href="/v6/switch.css">
<link rel="stylesheet" href="/v6/rating.css">

# LemonadeJS Studio block catalog

LemonadeJS Studio is the first-party block catalog: **43 components, 786 contract
checks, every one passing** before the catalog is allowed to build. A
block is not a snippet. It is a publishable npm package that ships with
a machine-readable contract, a conformance proof, generated
documentation, a playground demo, and three deployment forms:

```javascript
import Modal from '@lemonadejs/modal';
import '@lemonadejs/modal/style.css';

html`<${Modal} title="Hello" closable draggable>...</${Modal}>`;
                                  // by value, no registration
setComponents({ Modal });         // then <Modal /> by name anywhere
createWebComponent(Modal);        // <lm-modal> in plain HTML, React, Vue
```

Two catalog blocks, by value, sharing one page: the same import, the same `bind`, the same contract rules.

<!--example-->

```js
import { html } from 'lemonadejs';
import Switch from '@lemonadejs/switch';
import Rating from '@lemonadejs/rating';

const App = (props, { state }) => {
    const enabled = state(true);
    const score = state(4);
    return html`<div>
        <${Switch} bind="${enabled}" label="Reviews enabled" />
        <p>${() => enabled.value && html`<${Rating} bind="${score}" />`} score: <b>${score}</b></p>
    </div>`;
};
```

## Component library considerations

A component library is a bundle of decisions. Adopting a block means
inheriting those decisions: coherent choices for the library's goals
that become conditions for the consumer:

- **A host framework.** Most block libraries target one framework; the
  host application needs that framework's runtime to use them.
- **A styling system.** Components are styled through the library's own
  engine, tokens or utility classes; customization requires learning
  that system.
- **Documented, not proven, interfaces.** The documentation says the
  component accepts `disabled`; the mechanism that keeps that claim
  true across versions is maintainer discipline and the changelog,
  nothing a consumer can run.
- **Floating-layer machinery.** Selects, menus, tooltips, dialogs and
  drawers all need positioning, stacking and dismissal. Libraries solve
  this with shared internal layers or a positioning dependency:
  machinery with its own surface to learn when behavior needs overriding.
- **Source-priced discovery.** For an agent, learning a component means
  reading its source or prose docs: thousands of tokens per block,
  re-paid every session.

The Studio's design brief chose a different set of conditions, described
below, with its own costs, listed at the end.

## Architecture

### One primitive, eight composers

The catalog's architecture decision is visible in its dependency graph:
**every floating surface is the Modal.** Eight blocks declare
`@lemonadejs/modal` as their only dependency and build on its behaviors
(open/close origins, anchoring, auto-adjust to the viewport, backdrop,
element-scoped Escape handling):

- **Contextmenu**: every menu level is a headerless, auto-adjusting
  Modal; submenus are a *stack* of them, flipping direction when out of
  space.
- **Dropdown**: select, autocomplete and picker anchor their panel on
  the Modal, with the datagrid's fixed-row-height virtualization inside.
- **Calendar**: date, datetime and range picker on the same anchored
  panel.
- **Color**: the picker panel, same anchor mechanics.
- **Drawer**: the Modal's `left`/`right` positions are already
  full-height side panels; the drawer adds anchor mapping and the
  slide-in. The bottom sheet is the same primitive.
- **Dialog**: confirm / alert / prompt as centered Modal sheets.
- **Actionsheet** is an iOS-style sheet: headerless Modal, position
  bottom, backdrop.
- **Schedule**: its built-in event editor opens on the Modal.

And it goes a level deeper: **Quickmenu**, **Toolbar** and **Topmenu**
each build on the Contextmenu, which is itself a stack of Modals, so
eleven of the forty-three blocks ultimately stand on the one primitive.

Composition is not code reuse for its own sake. It is where fixes
flow. When the Modal stopped snapshotting its anchor coordinates at
construction and began re-reading them per open (the bug documented in
[computed()](/docs/computed/)), panel placement was repaired in every
block that anchors on it, in the same commit. One primitive fixed,
every composer repaired. The difference from the shared internal layers
above is not the sharing. It is that the shared layer is itself a
published, contract-verified block, with the same public surface its
composers use.

The Modal earns the load it carries: 49 contract checks, 8-edge resize,
viewport-clamped dragging, a minimize dock, and the destroy receipt:
100,000 create/open/drag/destroy cycles with zero detached DOM nodes
(see [Destroy](/docs/destroy/)).

### The catalog

| Block | Package | Checks | Notes |
|---|---|---|---|
| <span id="block-accordion"></span>[Accordion](/docs/plugins/accordion/) | `@lemonadejs/accordion` | 9 | |
| <span id="block-actionsheet"></span>[Actionsheet](/docs/plugins/actionsheet/) | `@lemonadejs/actionsheet` | 13 | sheet on the Modal primitive |
| <span id="block-alert"></span>[Alert](/docs/plugins/alert/) | `@lemonadejs/alert` | 15 | |
| <span id="block-backdrop"></span>[Backdrop](/docs/plugins/backdrop/) | `@lemonadejs/backdrop` | 13 | |
| <span id="block-button"></span>[Button](/docs/plugins/button/) | `@lemonadejs/button` | 22 | |
| <span id="block-buttongroup"></span>[Buttongroup](/docs/plugins/buttongroup/) | `@lemonadejs/buttongroup` | 18 | |
| <span id="block-calendar"></span>[Calendar](/docs/plugins/calendar/) | `@lemonadejs/calendar` | 39 | date/datetime/range picker on the Modal primitive |
| <span id="block-card"></span>[Card](/docs/plugins/card/) | `@lemonadejs/card` | 20 | |
| <span id="block-carousel"></span>[Carousel](/docs/plugins/carousel/) | `@lemonadejs/carousel` | 14 | single-file: styles ship inside the component |
| <span id="block-color"></span>[Color](/docs/plugins/color/) | `@lemonadejs/color` | 14 | picker panel on the Modal primitive |
| <span id="block-contextmenu"></span>[Contextmenu](/docs/plugins/contextmenu/) | `@lemonadejs/contextmenu` | 6 | levels are a stack of Modals |
| <span id="block-cropper"></span>[Cropper](/docs/plugins/cropper/) | `@lemonadejs/cropper` | 21 | |
| <span id="block-datagrid"></span>[Datagrid](/docs/plugins/datagrid/) | `@lemonadejs/datagrid` | 23 | virtualized big-data grid (100k rows, 5ms mount) |
| <span id="block-dialog"></span>[Dialog](/docs/plugins/dialog/) | `@lemonadejs/dialog` | 19 | confirm/alert/prompt on the Modal primitive |
| <span id="block-drawer"></span>[Drawer](/docs/plugins/drawer/) | `@lemonadejs/drawer` | 15 | side panels + bottom sheet on the Modal primitive |
| <span id="block-dropdown"></span>[Dropdown](/docs/plugins/dropdown/) | `@lemonadejs/dropdown` | 39 | select/autocomplete/picker on the Modal primitive |
| <span id="block-formify"></span>[Formify](/docs/plugins/formify/) | `@lemonadejs/formify` | 8 | your markup in, one data object out |
| <span id="block-gantt"></span>[Gantt](/docs/plugins/gantt/) | `@lemonadejs/gantt` | 24 | %-positioned bars that align across instances |
| <span id="block-imagelist"></span>[Imagelist](/docs/plugins/imagelist/) | `@lemonadejs/imagelist` | 14 | |
| <span id="block-kanban"></span>[Kanban](/docs/plugins/kanban/) | `@lemonadejs/kanban` | 7 | card DOM identity survives cross-column moves (one flat keyed list) |
| <span id="block-list"></span>[List](/docs/plugins/list/) | `@lemonadejs/list` | 26 | search, pagination, remote mode |
| <span id="block-login"></span>[Login](/docs/plugins/login/) | `@lemonadejs/login` | 36 | multi-screen authentication flows |
| <span id="block-modal"></span>[Modal](/docs/plugins/modal/) | `@lemonadejs/modal` | 49 | **the platform primitive** |
| <span id="block-navbar"></span>[Navbar](/docs/plugins/navbar/) | `@lemonadejs/navbar` | 13 | |
| <span id="block-progress"></span>[Progress](/docs/plugins/progress/) | `@lemonadejs/progress` | 15 | |
| <span id="block-quickmenu"></span>[Quickmenu](/docs/plugins/quickmenu/) | `@lemonadejs/quickmenu` | 12 | |
| <span id="block-rating"></span>[Rating](/docs/plugins/rating/) | `@lemonadejs/rating` | 20 | |
| <span id="block-router"></span>[Router](/docs/plugins/router/) | `@lemonadejs/router` | 11 | |
| <span id="block-schedule"></span>[Schedule](/docs/plugins/schedule/) | `@lemonadejs/schedule` | 35 | event editor on the Modal primitive |
| <span id="block-signature"></span>[Signature](/docs/plugins/signature/) | `@lemonadejs/signature` | 21 | |
| <span id="block-slider"></span>[Slider](/docs/plugins/slider/) | `@lemonadejs/slider` | 20 | |
| <span id="block-speeddial"></span>[Speeddial](/docs/plugins/speeddial/) | `@lemonadejs/speeddial` | 18 | |
| <span id="block-switch"></span>[Switch](/docs/plugins/switch/) | `@lemonadejs/switch` | 22 | native checkbox core |
| <span id="block-tabs"></span>[Tabs](/docs/plugins/tabs/) | `@lemonadejs/tabs` | 18 | |
| <span id="block-timeline"></span>[Timeline](/docs/plugins/timeline/) | `@lemonadejs/timeline` | 32 | |
| <span id="block-toast"></span>[Toast](/docs/plugins/toast/) | `@lemonadejs/toast` | 11 | |
| <span id="block-toggle"></span>[Toggle](/docs/plugins/toggle/) | `@lemonadejs/toggle` | 14 | |
| <span id="block-toolbar"></span>[Toolbar](/docs/plugins/toolbar/) | `@lemonadejs/toolbar` | 10 | |
| <span id="block-tooltip"></span>[Tooltip](/docs/plugins/tooltip/) | `@lemonadejs/tooltip` | 13 | |
| <span id="block-topmenu"></span>[Topmenu](/docs/plugins/topmenu/) | `@lemonadejs/topmenu` | 4 | |
| <span id="block-transferlist"></span>[Transferlist](/docs/plugins/transferlist/) | `@lemonadejs/transferlist` | 12 | |
| <span id="block-treeview"></span>[Treeview](/docs/plugins/treeview/) | `@lemonadejs/treeview` | 7 | one recursive keyed view function |
| <span id="block-wheel"></span>[Wheel](/docs/plugins/wheel/) | `@lemonadejs/wheel` | 14 | |

### What every block guarantees

Each row in that table is backed by the same set of shipped artifacts,
none of them hand-maintained:

- **`contract.json`**: the machine-readable interface (props with
  types and defaults, bind, events, api), exported by the package
  itself. An agent decides "is this the block I need?" without reading
  source. See [Contracts](/docs/contracts/).
- **`verify.json`**, the conformance proof: `verify(Block)` exercises
  every declared prop (as plain value *and* as live state), every
  event, bind, and the api actually exposed, and fails on any engine
  warning during any check. The "Checks" column above counts these.
- **The registry gate.** `npm run registry` regenerates every contract
  and proof; one failing block fails the build. A block whose proof
  fails does not enter the catalog: enforced, not reviewed.
- **A generated README**, synthesized from the source header, the
  contract's inline comments and the proof. Documentation cannot drift
  from the interface because it is a projection of it.
- **A playground demo**: `npm run dev` serves every block's demo page.
- **Three deployments, one component**: by value, by name, and as a
  custom element; the React adapter derives from the same contract. See
  [Deployments](/docs/deployments/) and [React](/docs/react/).
- **Destroy-gated.** Drag systems hold one persistent cleanup, suites
  assert listener balance, and the engine-level heap/WeakRef gates run
  on every change ([Destroy](/docs/destroy/)).

## Distribution models

Component distribution today follows three broad models, each serving
its purpose well:

| | Framework libraries | Design-system libraries | Copy-source kits | Studio |
|---|---|---|---|---|
| Framework reach | one framework | one framework | one framework + its styling stack | any: native, custom element, React adapter |
| Styling | the library's styling engine and theme system | design language, tokens | utility classes you own | plain CSS, `lm-<name>-*` classes, `data-*` variants |
| Distribution | npm dependency | npm dependency | code copied into your repo | npm package, a few KB, zero deps beyond composed primitives |
| Machine-readable schema | TS types (compile-time) | TS types (compile-time) | the source itself | `contract.json` ships in the package |
| Conformance proof | the test suite, maintainer-side | the test suite, maintainer-side | your responsibility | `verify.json`, regenerated and gated |
| Docs currency | maintained by hand | maintained by hand | your responsibility | generated from the contract |

Differences that favor the established models:

- **Theming depth.** Mature design-token systems, palettes, dark-mode
  infrastructure and styling escape hatches are far more than `lm-*`
  classes plus CSS variables. An organization living in a themed design
  system is better served by machinery built for that.
- **Accessibility maturity.** Years of hardening in focus management
  and screen-reader behavior; the Studio Modal's focus trap is still on
  the roadmap (a named TODO, not a hidden gap).
- **Breadth and battle-testing.** Hundreds of components, millions of
  installs, every edge case already filed by someone. Studio is 43
  blocks and a beta.
- **The copy-source ownership model.** Copying readable source into
  your repo so you can edit it gives full control. Studio blocks are
  one readable file because the same property matters. A Studio fork
  additionally keeps something mechanical to check edits against:
  `verify()` still runs.

What the Studio adds is the right side of the table: a contract an
agent reads in one request, and a proof that the implementation honors
it, regenerated, and refused at the gate when it fails.

## In practice

A settings panel: one block composed inside another, two-way bound,
then deployed to a non-lemonade host without changes.

```javascript
import { html, mount, ref, createWebComponent } from 'lemonadejs';
import Modal from '@lemonadejs/modal';
import Switch from '@lemonadejs/switch';

const Settings = (props, { state }) => {
    const open = state(false);
    const dark = state(false);

    return html`<div>
        <button onclick="${() => (open.value = true)}">Settings</button>
        <${Modal} bind="${open}" title="Settings" closable draggable>
            <${Switch} bind="${dark}" label="Dark mode"
                onchange="${(v) => document.body.classList.toggle('dark', v)}" />
        </${Modal}>
    </div>`;
};

mount(Settings, document.getElementById('root'));

// The same blocks in a non-lemonade page, no rewrite:
createWebComponent(Modal);    // <lm-modal title="Settings" closable>
createWebComponent(Switch);   // <lm-switch label="Dark mode">
```

`bind` is the Modal's open state (two-way: closing from the X writes
`false` back) and the Switch's checked state: the same
[binding model](/docs/two-way-binding/) on every block, because every
block is built from the same recipe. That recipe, including how to add
a block of your own, is the next chapter:
[Building blocks](/docs/building-blocks/).

## Reference

```
@lemonadejs/<name>                  one package per block
    import Block from '@lemonadejs/<name>';
    import '@lemonadejs/<name>/style.css';
    import contract from '@lemonadejs/<name>/contract.json';

components/<name>/ (in the repo)
    src/index.ts, src/style.css     the implementation
    contract.json, verify.json      generated interface + proof
    README.md                       generated documentation
    demo.ts / demo.html             playground page
components/registry.json            all 43 contracts, one request

npm run dev                         playground on :3000
npm run registry                    regenerate contracts/proofs: THE GATE
npm run docs                        regenerate READMEs + catalog index
```

Blocks compose like any component ([Components](/docs/components/)),
verify like any contract ([Tests](/docs/tests/)), and die cleanly or do
not ship ([Destroy](/docs/destroy/)).