Getting started
Terracotta is a headless UI library for SolidJS. Each component gives you behaviour, keyboard handling and ARIA wiring. None of them give you styles. You choose what the markup renders as, and what it looks like.
Install
npm i terracotta@next
# or
pnpm add terracotta@next
# or
yarn add terracotta@nextTerracotta 2 is published under the next tag while it is a release candidate.
The latest tag still points at the 1.x line, which targets Solid 1 and is
imported from the package root rather than one entry per component.
solid-js and @solidjs/web are peer dependencies. Terracotta 2 targets
Solid 2 (^2.0.0-rc.13).
The published code needs Node 20 or later, and uses syntax up to ES2022, such as class static blocks. A toolchain that cannot parse that has to transpile the package itself.
Imports
Terracotta has no barrel entry point. Every component is its own entry, named after itself, and the state primitives share one:
import { Disclosure } from 'terracotta/disclosure';
import { Listbox } from 'terracotta/listbox';
import { useDisclosureState } from 'terracotta/states';Each page in this documentation opens with the exact import line for what it covers. Importing a component pulls in that component and nothing else, so a page that uses one widget never pays for the other twenty-two.
Your first component
Every widget is a set of parts that you compose. Terracotta renders nothing beyond the elements you write, and adds no class names to them.
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
} from 'terracotta/disclosure';
function Details() {
return (
<Disclosure defaultOpen={false}>
<DisclosureButton class="trigger">Details</DisclosureButton>
<DisclosurePanel class="panel">Everything worth knowing.</DisclosurePanel>
</Disclosure>
);
}Those few lines already cover what an accessible disclosure needs. The button
carries aria-expanded. While the panel is open, the button points at it with
aria-controls. While it is closed, the panel is not in the DOM at all.
The mental model
Four ideas apply to every component in the library:
- You own the markup. Each part renders a default element. Use
asto swap it for another element or your own component. See rendering. - You own the state, or the component does. Pass
defaultOpenand the component manages itself. PassisOpenand you drive it. See state. - You own the styling. State reaches CSS through
tc-attributes, never through class names. See styling. - The library owns the accessibility. Roles, ARIA relationships, focus management and keyboard handling come with the component. See keyboard and focus.
The Claude Code plugin
Terracotta ships a plugin for Claude Code.
It carries a skill that describes the component catalogue and every part name,
the subpath imports, controlled versus uncontrolled state, render props, as
and prop passthrough, the tc- attributes, unmount, transitions, and the
keyboard and focus behaviour, with a copy of this reference material alongside
it, so the detail is available without a fetch.
/plugin marketplace add lxsmnsyc/terracotta
/plugin install terracotta@terracottaNothing else needs configuring. The skill declares the situations it applies to,
so it loads on its own once you are working in SolidJS code that imports from
terracotta, and stays out of the way otherwise. Check that it registered with:
/pluginIt documents Terracotta 2 and marks where 1.x differs, chiefly that 1.x has a
package root entry, so import { Dialog } from 'terracotta' is right there and
resolves to nothing here.
Where to go next
- Component reference: every component, part by part
- State primitives: the reactive stores underneath, which you can also use on their own