Morphos
Guide

Getting Started

Install Morphos and build your first headless primitive component.

This guide gets you from zero to a working, accessible component in a few minutes. If you'd rather understand the ideas first, read the Introduction — otherwise, let's build something.

Install PraxisJS

Morphos is built on top of PraxisJS, so it needs to be installed first.

npm install @praxisjs/core @praxisjs/decorators @praxisjs/runtime @praxisjs/jsx
pnpm add @praxisjs/core @praxisjs/decorators @praxisjs/runtime @praxisjs/jsx
yarn add @praxisjs/core @praxisjs/decorators @praxisjs/runtime @praxisjs/jsx
bun add @praxisjs/core @praxisjs/decorators @praxisjs/runtime @praxisjs/jsx

Install Morphos

@morphos/core is a required peer dependency of every component package — install it alongside whichever categories you actually need.

npm install @morphos/core
pnpm add @morphos/core
yarn add @morphos/core
bun add @morphos/core

Then add the component packages for what you're building:

npm install @morphos/inputs @morphos/overlays @morphos/layout @morphos/feedback
pnpm add @morphos/inputs @morphos/overlays @morphos/layout @morphos/feedback
yarn add @morphos/inputs @morphos/overlays @morphos/layout @morphos/feedback
bun add @morphos/inputs @morphos/overlays @morphos/layout @morphos/feedback

You don't have to install all four — each one is independent. A form-heavy page might only need @morphos/inputs; a dashboard with modals might add @morphos/overlays on top.

Configure TypeScript

Morphos requires the same TypeScript settings as PraxisJS:

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "useDefineForClassFields": false,
    "jsx": "react-jsx",
    "jsxImportSource": "@praxisjs/jsx"
  }
}

Build a simple component

Every Morphos component is a plain PraxisJS class component — no special setup, no providers. Here's a counter built with Button:

import { Component, State } from '@praxisjs/decorators'
import { StatefulComponent } from '@praxisjs/core'
import { Button } from '@morphos/inputs'

@Component()
class Counter extends StatefulComponent {
  @State() count = 0

  render() {
    return (
      <Button onClick={() => this.count++}>
        Clicked {() => this.count} times
      </Button>
    )
  }
}

Build a compound component

Components with multiple moving parts — dialogs, accordions, menus — follow the same pattern throughout Morphos: create the root instance once with @State(), then pass it down to each part as a prop named after the component.

import { Component, State } from '@praxisjs/decorators'
import { StatefulComponent } from '@praxisjs/core'
import {
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogTitle,
  DialogDescription,
  DialogClose,
} from '@morphos/overlays'

@Component()
class ConfirmEmail extends StatefulComponent {
  @State() dialog = new Dialog()

  render() {
    return (
      <>
        <DialogTrigger dialog={this.dialog}>Change email</DialogTrigger>

        <DialogContent dialog={this.dialog} aria-labelledby="dialog-title">
          <DialogTitle id="dialog-title">Change your email</DialogTitle>
          <DialogDescription>
            We'll send a confirmation link to your new address.
          </DialogDescription>
          <DialogClose dialog={this.dialog}>Cancel</DialogClose>
        </DialogContent>
      </>
    )
  }
}

No context provider, no hooks — this.dialog already knows whether it's open, traps focus while it is, locks page scroll, and closes on Escape or backdrop click, all out of the box. Every compound component in the library (Accordion, RadioGroup, Select, Toast, and the rest) follows this exact same shape, so once you've built one you've basically built them all.

Every component page's Usage section shows this pattern applied to that specific component — check the full component list once you're ready to explore further.

Style what you built

Morphos ships zero CSS. Every piece of state is exposed as a data-* attribute on the root element, so plain CSS selectors are all you need:

/* Style the disabled state */
[data-disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Style an open dialog */
[role="dialog"][data-open] {
  display: block;
}

/* Style a selected tab */
[role="tab"][data-selected] {
  border-bottom: 2px solid currentColor;
  font-weight: 600;
}

No class toggling required — the component sets and clears the attribute for you. See the full list of attributes in the Introduction.

Prefer not to write these from scratch? @morphos/styles ships an optional, opt-in CSS recipe for every component — install it and add one class name.

Where to go next

  • Styling — using the optional @morphos/styles package and its design tokens.
  • Inputs, Overlays, Layout, Feedback — browse every component's full Props and data-* reference.

On this page