---
title: "Deploying Components Three Ways"
description: "Every published component deploys three ways from one source: embedded by value, registered by name, or as a real custom element. One block, three forms."
source: https://lemonadejs.com/docs/deployments/
---

# Deploying components

Deploying a published component is not a choice you make once. The same
block, with the same import, contract and behavior, deploys three ways:

```javascript
import Datagrid from '@lemonadejs/datagrid';

// 1. by value: import and embed, no registration
html`<${Datagrid} data="${rows}" columns="${columns}" />`

// 2. by name: register once, use anywhere
setComponents({ Datagrid });
html`<Datagrid data="${rows}" columns="${columns}" />`

// 3. as a custom element: any host, plain HTML, Vue, Rails, anything
createWebComponent(Datagrid);            // defines <lm-datagrid>
```

This is not three component models. It is one component and three entry
points, and the library's own suite proves all three against the real
datagrid block (`tests/deployments.test.ts` in the lemonadejs repo).

One component, three deployment forms on the same page: by value, by name and as a custom element.

<!--example-->

```js
import { html, component, setComponents, createWebComponent } from 'lemonadejs';

const Tag = component('tag', { label: 'tag', color: '#868e96' }, (props) =>
    html`<span style="${() => 'padding:3px 12px;border-radius:12px;color:#fff;background:' + props.color.value}">${props.label}</span>`);

setComponents({ Tag });              // 2. by name
createWebComponent(Tag);             // 3. custom element: <lm-tag>

const App = () => html`<p>
    <${Tag} label="by value" color="#2b8a3e" />
    <Tag label="by name" color="#1971c2" />
    <lm-tag label="custom element" color="#e8590c"></lm-tag>
</p>`;
```

## Deployment forms

**By value, the default.** `<${Datagrid} />` embeds the imported
function directly. No registration, no global namespace; the reference
is tracked by TypeScript and refactoring tools, and bundlers tree-shake
what you never import. Studio blocks are independent npm packages
(`@lemonadejs/datagrid`, `@lemonadejs/switch`, …), so an app pays only
for the blocks it uses.

**By name, the app vocabulary.** `setComponents({ Datagrid })` once,
then `<Datagrid />` in any template, like an HTML tag your application
taught itself. Names are case-sensitive and resolved at mount; a
misspelled or unregistered name raises `LJS-104` with the registration
hint instead of rendering nothing. Both forms take identical props;
see [Components](/docs/components/) for the prop rules they share.

**As a custom element, any host.** `createWebComponent(Datagrid)`
derives a real `<lm-datagrid>` element from the contract: live coerced
attributes, element properties, CustomEvents out, destroy on removal.
The full derivation is the [Web components](/docs/web-components/)
chapter; the point here is that it is the *same block*, not a wrapper
package.

One engine detail makes the third form composable with the first two:
inside a lemonade template, **object slot values become element
properties**. When the renderer applies `data="${rows}"` to a DOM
element and the value is an object or function, it assigns the property
instead of stringifying an attribute, so a custom element receives the
real array:

```javascript
// works inside a lemonade template: rows is passed as a property,
// never serialized through an attribute
html`<lm-datagrid data="${rows}" columns="${columns}" pagination="${3}"></lm-datagrid>`
```

There is a fourth form for one specific host: `adaptReact(Datagrid)`
produces a first-class React component, derived from the same contract;
see [React](/docs/react/).

Every published block carries all three forms by construction, and the
test suite holds the promise.

## In practice

`tests/deployments.test.ts` in the library repo is the proof, written
against the real datagrid, not a toy:

- **By name:** `setComponents({ Datagrid })`, then a template using
  `<Datagrid data="${rows}" columns="${columns}" pagination="${4}" />`
  asserts four rendered rows and the pagination label `1–4 of 6 rows`.
- **As a custom element:** `createWebComponent(Datagrid)` returns
  `'lm-datagrid'`; the test creates the element, assigns `el.data`,
  `el.columns`, `el.pagination = 4` as plain properties, appends it, and
  asserts the rows. Then `el.pagination = 2` and the grid re-renders live
  through the contract-derived property accessor. Then `el.remove()`, and
  one microtask later the instance is destroyed, the v6 auto-unmount
  policy (see [Destroy](/docs/destroy/)).
- **Custom element inside a lemonade template:** `<lm-datagrid
  data="${rows}" ...>` receives the array as a property and renders:
  the object-slot rule above, asserted.

Every Studio block's playground exercises the same span: the switch
demo registers `<lm-switch>` with one line and uses `<${Switch} />` by
value in the same file. The [Studio](/docs/studio/) registry lists all
43 blocks; each carries the contract that makes the three forms
derivable.

## Reference

```javascript
// 1. by value: no registration
html`<${Component} prop="${value}" />`

// 2. by name: register once (case-sensitive), use anywhere
import { setComponents } from 'lemonadejs';
setComponents({ Component });
html`<Component prop="${value}" />`        // unknown name → LJS-104 at mount

// 3. custom element: from a contract, zero options
import { createWebComponent } from 'lemonadejs';
const tag = createWebComponent(Component);  // → 'lm-component'
```

Rules worth memorizing: both template forms take identical props; names
are case-sensitive; literal attributes are strings, `${...}` passes by
reference; object slot values applied to DOM elements become element
properties, which is exactly what custom elements expect. For the full
custom-element surface, read [Web components](/docs/web-components/);
for React hosts, [React](/docs/react/).