# @qds.dev/ui

**Headless, accessible UI components for Qwik**

[![npm version](https://img.shields.io/npm/v/@qds.dev/ui.svg?style=flat-square)](https://www.npmjs.com/package/@qds.dev/ui)

## Overview

Building accessible UI components is complex. You need to handle keyboard support, assistive technologies, and state management—challenges that traditionally require significant time and resources to get right.

`@qds.dev/ui` abstracts this complexity into base components. This separation encapsulates logic and accessibility behaviors while leaving styling and structure completely flexible.

## Components

This is the core of QDS, providing the [essential building blocks / LEGO bricks you need](https://www.smashingmagazine.com/2022/05/you-dont-need-ui-framework/). It includes accessible primitives, such as:

- **Inputs:** Checkbox, Switch, Slider, OTP, Select, Radio Group
- **Overlays:** Modal, Popover, Tooltip
- **Navigation:** Tabs, Accordion, Menu, Navbar, Pagination
- **Layout:** Collapsible, Separator, Resizable, Carousel
- **Feedback:** Progress, QR Code, Toggle

See [`src/`](./src/) for the full component list.

## Installation

### CLI

```bash
npm create qds@latest
```

The command above creates a new QDS project with the latest version of QDS, Qwik, and prompts you for the features you want to include in your project.

### Manual

```bash
npm install @qds.dev/ui
```

> This does not include icons, composition, and bundling related features. For those, you need to also add the vite plugins from `@qds.dev/tools/vite` and consume them in `vite.config.ts` in the `plugins` array. Look at the README in the tools package for more information on the plugins.

**Peer dependencies:**

- `@qwik.dev/core`

**Note:** @qds.dev/base is a devDependency used at build time and bundled into the @qds.dev/ui output. You do not need to install @qds.dev/base separately.

## Quick Start

### Basic Checkbox

```tsx
import { component$ } from "@qwik.dev/core";
import { Checkbox } from "@qds.dev/ui";

export default component$(() => {
  return (
    <Checkbox.Root>
      <Checkbox.Trigger>
        <Checkbox.Indicator>Indicator</Checkbox.Indicator>
      </Checkbox.Trigger>
      <Checkbox.Label>Accept terms</Checkbox.Label>
    </Checkbox.Root>
  );
});
```

### Changing state

QDS has two types of state:

- Value based state (literal values, stores, the signal.value property, anything that resolves to a value)
- Signal based state (the signal itself)

Below is an example of where signal based state is used to keep track of whether someone has accepted the checkbox.

```tsx
import { component$, useSignal } from "@qwik.dev/core";
import { Checkbox, Lucide } from "@qds.dev/ui";

export default component$(() => {
  const accepted = useSignal(false);

  return (
    <>
      <Checkbox.Root bind:checked={accepted}>
        <Checkbox.Trigger>
          <Checkbox.Indicator>
            <Lucide.Check />
          </Checkbox.Indicator>
        </Checkbox.Trigger>
        <Checkbox.Label>Accept terms</Checkbox.Label>
      </Checkbox.Root>
      <p>Status: {accepted.value ? "Accepted" : "Not accepted"}</p>
    </>
  );
});
```

> Signal based state in QDS is two way binding. The consumer or the internals of the component can change the signal's value.

## Composition

Components by design are modular pieces. A core principle is that the consumer should clearly see and understand what is being rendered.

Below are some examples of some consumer made abstractions:

### Specific Component

```tsx
export const TermsCheckbox = component$(() => {
  const context = useContext(formContextId);

  return (
    <Checkbox.Root bind:checked={context.termsAccepted}>
      <Checkbox.Trigger>
        <Checkbox.Indicator>
          <Lucide.Check />
        </Checkbox.Indicator>
      </Checkbox.Trigger>
      <Checkbox.Label>I accept the terms and conditions</Checkbox.Label>
    </Checkbox.Root>
  );
});
```

### Generalized Component

```tsx
type CheckboxFieldProps = PropsOf<typeof Checkbox.Root> & { label: string };

export const CheckboxField = component$<CheckboxFieldProps>(({ label, ...props }) => {
  return (
    <Checkbox.Root {...props}>
      <Checkbox.Trigger>
        <Checkbox.Indicator>
          <Lucide.Check />
        </Checkbox.Indicator>
      </Checkbox.Trigger>
      <Checkbox.Label>{label}</Checkbox.Label>
    </Checkbox.Root>
  );
});

export default component$(() => {
  const newsletter = useSignal(false);
  const marketing = useSignal(false);

  return (
    <form>
      <CheckboxField label="Subscribe to newsletter" bind:checked={newsletter} />
      <CheckboxField label="Receive marketing emails" bind:checked={marketing} />
    </form>
  );
});
```

### Component templates

```tsx
  export default component$(() => {
    const username = (
      <Textbox.Root>
        <Textbox.Label>Username</Textbox.Label>
        <Textbox.Input />
        <Textbox.Description>Enter your username</Textbox.Description>
        {isNoUsername && <Textbox.Error>Username is required</Textbox.Error>}
      </Textbox.Root>
    )

    const password = (
      <Textbox.Root>
        <Textbox.Label>Password</Textbox.Label>
        <Textbox.Input type="password" />
        <Textbox.Description>Enter your password</Textbox.Description>
        {isNoPassword && <Textbox.Error>Password is required</Textbox.Error>}
      </Textbox.Root>
    )

    return (
      <form onSubmit$={...}>
        {username}
        {password}
      </form>
    );
  });
```

## Architecture

For package internals, dependency relationships, and design decisions (including the compound component pattern and Render component rationale), see [ARCHITECTURE.md](./ARCHITECTURE.md).

## Component Structure

Each component is exported as a namespace with sub-components (e.g., `Checkbox.Root`, `Checkbox.Trigger`, `Checkbox.Label`).

Checkbox in this case is the namespace that we export in the library. With the object properties being the Qwik components.

For more internal usage examples, see the `src/` directory. Each component in the docs project has scenario files demonstrating various configurations.

### Why the need for the namespace?

Components are exported as namespaces (`Checkbox.*`, `Modal.*`) rather than named exports (`CheckboxRoot`, `ModalTrigger`) for cleaner imports and better IDE autocomplete. When you type `Checkbox.`, you see all related subcomponents immediately.

## Related Packages

**Built with:**

- [@qds.dev/base](https://www.npmjs.com/package/@qds.dev/base) - Logic that we re-use and share across multiple packages in this repository.

**Complements:**

- [@qds.dev/motion](https://www.npmjs.com/package/@qds.dev/motion) - Create native feeling experiences and animations.
- [@qds.dev/code](https://www.npmjs.com/package/@qds.dev/code) - Show a live updating sandbox with a performant code editor and preview.

## Known Limitations

- **Browser support**: Many of the components use modern CSS features (css anchor positioning, popover api, etc) and may not work in older browsers.

## Documentation

Browse the [`docs/`](/docs/) folder for:

- Component examples with scenarios
- Accessibility implementation details
- API reference

## Contributing

We welcome contributions. See [CONTRIBUTING.md](/CONTRIBUTING.md) for setup instructions and  
[ARCHITECTURE.md](./ARCHITECTURE.md) for how this package is structured internally.
