---
language: "en"
title: "Getting started"
canonical: "https://uniaria.tenphi.me/guide/getting-started/"
source: "docs/guide/getting-started.mdx"
index: true
---

# Getting started

UniAria is at 0.x on npm: its APIs can change in any 0.x version.

## Install

Pick the adapter for your stack. Each brings `@uniaria/core`, where the
behavior definitions live.

```sh
pnpm add @uniaria/core @uniaria/react
```

React 18 or 19.

```sh
pnpm add @uniaria/core @uniaria/vue
```

Vue 3.5 or later.

```sh
pnpm add @uniaria/core @uniaria/elements
```

For plain HTML, server templates, and any framework that renders HTML.

The packages are ES modules only and target ES2022. They support the browsers
in [Baseline widely available](/guide/browser-support/), and every entry imports
safely on a server.

## React

Import a component's definition from its subpath of `@uniaria/core`, pass it
to `useBehavior` with props, and spread each part's props onto your element:

```tsx
import { button } from '@uniaria/core/button';
import { useBehavior } from '@uniaria/react';

export function SaveButton({ onSave }: { onSave(): void }) {
  const save = useBehavior(button, { onPress: onSave });
  return <button {...save.parts.root}>Save</button>;
}
```

`save.parts.root` carries the attributes, event handlers, a `ref` and the
marker `data-uni="button"`, which your CSS can select. Add your own
attributes next to it, `className` included: `class` is all yours. Leave ARIA
state and the `data-*` attributes to UniAria: it writes them itself, and
warns in development when you pass one.

A component with several parts gives you one set of props per part:

```tsx
import { toggleSwitch } from '@uniaria/core/switch';
import { useBehavior } from '@uniaria/react';

export function WifiSwitch() {
  const wifi = useBehavior(toggleSwitch, {
    defaultSelected: true,
    labelElementType: 'span',
    onSelectedChange: (isSelected) => console.log(isSelected),
  });
  return (
    <label {...wifi.parts.root}>
      <input {...wifi.parts.input} name="wifi" />
      <span {...wifi.parts.indicator} />
      <span {...wifi.parts.label}>Wi-Fi</span>
    </label>
  );
}
```

Hovering, pressing or toggling the switch doesn't render `WifiSwitch` again:
UniAria writes the new state straight to the elements. When your render needs
a value, select it with `useBehaviorState`. See [State](/guide/state/).

## Vue

The same definitions work in Vue. Pass one to `useBehavior` in
`<script setup>`, with props, and bind each part's props onto your element
with `v-bind`:

```vue
<script setup lang="ts">
import { button } from '@uniaria/core/button';
import { useBehavior } from '@uniaria/vue';

const emit = defineEmits<{ save: [] }>();
const save = useBehavior(button, { onPress: () => emit('save') });
</script>

<template>
  <button v-bind="save.parts.root">Save</button>
</template>
```

As in React, `save.parts.root` carries the attributes, event handlers, a `ref`
and the `data-uni="button"` marker. Write your own attributes after `v-bind`,
`class` included, and leave ARIA state and the `data-*` attributes to
UniAria. Props that change come as a getter, which reads your props and refs
like `computed()` does:

```vue
<script setup lang="ts">
import { toggleSwitch } from '@uniaria/core/switch';
import { useBehavior } from '@uniaria/vue';

const props = defineProps<{ isOffline: boolean }>();

const wifi = useBehavior(toggleSwitch, () => ({
  defaultSelected: true,
  isDisabled: props.isOffline,
  labelElementType: 'span',
}));
</script>

<template>
  <label v-bind="wifi.parts.root">
    <input v-bind="wifi.parts.input" name="wifi" />
    <span v-bind="wifi.parts.indicator" />
    <span v-bind="wifi.parts.label">Wi-Fi</span>
  </label>
</template>
```

Toggling the switch doesn't render this component again either. When your
template needs a value, read it with `useBehaviorState`. See
[Vue](/api/vue/).

## Custom elements

Import a component's registering entry once, anywhere on the page. It defines
the element, `<uni-` plus the component name, and wraps your markup. Mark the
root part with `data-uni` and the component name, and every other part with
`data-element`, its name capitalized (`Input`, `Indicator`):

```html
<uni-button id="save">
  <button data-uni="button">Save</button>
</uni-button>

<script type="module">
  import '@uniaria/elements/button';

  document.querySelector('#save').addEventListener('press', () => save());
</script>
```

The element has no shadow root and renders nothing: your `<button>` stays the
button, styled by your CSS. Props become attributes in kebab case, without an
`is` or `should` prefix (`isDisabled` is `disabled`), and properties with the
prop's own name (`element.isDisabled`). Events are lowercase DOM events with
the value in `detail` (`pressstart`, `selectedchange`).

The element reads its starting state from your markup where HTML has it, such
as a checkbox's `checked` or a link's `href`, so server-rendered HTML works
before JavaScript runs.

### Another prefix

To register a component under your own name, call `defineElement` with its
definition instead of importing the registering entry:

```ts
import { button } from '@uniaria/core/button';
import { defineElement } from '@uniaria/elements';

defineElement(button, { prefix: 'acme' }); // <acme-button>
```

The parts keep their markers: the root is still `data-uni="button"`.

### In other frameworks

The elements work wherever HTML does. Tell your framework that `uni-*` tags
are custom elements if it asks (Vue's `compilerOptions.isCustomElement`, for
example, though Vue has [its own adapter](#vue) too), listen for the
lowercase events, and set controlled values as properties:

```html
<!-- Vue -->
<uni-switch :isSelected="wifi" @selectedchange="wifi = $event.detail">
  <label data-uni="switch">
    <input data-element="Input" type="checkbox" />
    <span data-element="Indicator"></span>
    <span data-element="Label">Wi-Fi</span>
  </label>
</uni-switch>
```

## Next steps

* [Styling](/guide/styling/): the attributes and variables your CSS can target.
* [State](/guide/state/): controlled values, events and reading state.
* [Components](/components/): every component with a live demo.
