---
title: "When Not to Use LemonadeJS: Limits and Tradeoffs"
description: "When not to use LemonadeJS: the cases where it is the wrong choice, stated plainly. SSR, ecosystem depth, compile-time optimization, and the trade-offs."
source: https://lemonadejs.com/docs/when-not/
---

# When not to use LemonadeJS

These are the situations where LemonadeJS is the wrong choice and another
tool is a better fit. Each is a
consequence of deliberate design choices; the relevant chapter is usually
one link away.

## You need server-side rendering

LemonadeJS v6 renders in the browser. There is no SSR, no streaming HTML,
no hydration story. If server-rendered first paint matters, as on content sites
where SEO and time-to-first-byte are the business, Next.js, Nuxt, Astro,
or SvelteKit address that; LemonadeJS does not. (This site is static Astro
with LemonadeJS for interactive islands, and that combination works well;
LemonadeJS *as* the SSR layer does not exist.)

## You target something other than the DOM

LemonadeJS renders to the browser DOM only. Other runtime targets (native
mobile, canvas, terminal, PDF) are not available. If your product needs
one component model across web and native, this is not the right tool.

## You depend on ecosystem depth

React's ecosystem spans hundreds of thousands of packages. LemonadeJS ships
43 first-party blocks and an engine. The contract system makes those blocks
unusually verifiable, but if your team's velocity comes from assembling
third-party packages, the ecosystem gap is real and will not close soon.

The same applies to hiring: React familiarity is a widely recognised
signal; LemonadeJS is not. A team that staffs against the market should
account for this.

## You want compile-time optimization

LemonadeJS has no compiler by design; zero build step is the feature. That
means it cannot do what Svelte or Solid's compilers do: analyze your
templates ahead of time, eliminate dead branches, or generate the
theoretically minimal update code. The runtime is small and updates are
fine-grained, but a compiled framework will always have optimizations a
runtime-only one structurally cannot. If you are at the scale where that
difference is measurable in your product, measure it.

## You need portals today

Floating UI (modals, dropdowns, tooltips) renders in place with
`position: fixed`. Inside an ancestor with `transform`, `filter` or
`perspective`, fixed positioning resolves against that ancestor and the
panel will appear in the wrong place, a CSS platform rule, not a bug, but
React's `createPortal` escapes it and LemonadeJS currently does not. A
portal design exists and was deliberately deferred until real-world demand
proves it earns its bytes. If your components must live inside transformed
containers (animation libraries, certain design systems), this is a present
limitation.

## You expect framework-managed accessibility

The blocks carry roles, keyboard activation and ARIA where the behavior
is unambiguous (dialog semantics and focus trapping in Modal, roving
tabindex in tabs, slider semantics in rating, grid semantics in datagrid),
but v6 has not had a comprehensive WCAG audit and remediation pass.
Libraries like React Aria or Radix exist specifically to own this problem
and are years ahead. If WCAG conformance is a launch requirement, audit
the blocks you need before committing; the known gaps are tracked in the
repository, not hidden.

## Costs that come with the design (even when it fits)

These are not missing features; they are the other side of decisions this
book defends elsewhere:

- **Mutation requires `touch()`.** State contents are mutable and mutation
  is silent until you notify. This is the model that makes a one-cell
  change in a million-row array O(1), and it is also a discipline, with a
  dev warning (LJS-201) instead of a compiler guarantee behind it.
- **Keys are scoped per list.** An item moving between two lists rebuilds
  on the other side, the same scoping React and Vue have, stated here so
  you do not discover it in a kanban board.
- **Styles are global by design.** `<style>` hoisting has no scoping
  engine; the `lm-*` prefix convention is the isolation mechanism. Teams
  wanting enforced style isolation will miss CSS modules or shadow DOM.
- **`resource()` is a fetch lifecycle, not a data layer.** No cache, no
  deduplication, no retries; React Query and SWR solve a deliberately
  larger problem. Compose with `store()` or bring a data library.
- **The dev/prod split is per build artifact.** Warnings, `trace()` and
  `explain()` live in the dev build only. You choose the artifact; there
  is no runtime flag.

## So when is it right?

When the authors of your frontend, human or agent, benefit from a system
small enough to hold entirely in context, components published with
machine-readable contracts, and a `verify()` step that turns "looks right"
into a pass/fail report. That case is the rest of this book, starting with
[why v6 exists](/docs/motivation/).

Where it fits: one interactive island inside otherwise static content, with nothing else on the page changing.

<!--example-->

```js
import { html } from 'lemonadejs';

const faq = [
    { q: 'Does it need a build step?', a: 'No. One import, in the browser.' },
    { q: 'Does it render on the server?', a: 'No. It targets the DOM; the page around it can be anything.' },
    { q: 'How big is it?', a: 'About 9 KB gzipped, zero dependencies.' },
];

const App = (props, { state }) => {
    const open = state(-1);
    return html`<div>${() => faq.map((item, i) => html`<div key="${i}">
        <p style="cursor:pointer;margin:6px 0" onclick="${() => (open.value = open.value === i ? -1 : i)}">
            <b>${open.value === i ? '▾' : '▸'} ${item.q}</b>
        </p>
        ${open.value === i && html`<p style="margin:0 0 8px 18px">${item.a}</p>`}
    </div>`)}</div>`;
};
```