> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lodemc.net/llms.txt
> Use this file to discover all available pages before exploring further.

# sdk/ui reference

> Hooks and the component kit

```tsx theme={null}
import { useProjectState, PageTitle, PrimaryButton, Icons } from 'sdk/ui'
```

Hooks and the app's own components, so your pages look like the rest of the app. React's own hooks (`useState`, `useEffect`, …) come from `react`.

## Hooks

### useProjectState

```ts theme={null}
const [value, setValue] = useProjectState('count', 0)
```

State that survives restarts, stored separately **per project**. Keyed by your expansion id, so two expansions can both keep a `count`.

### useGlobalState

```ts theme={null}
const [theme, setTheme] = useGlobalState('theme', 'dark')
```

The same, but shared across every project, for a preference that belongs to the expansion.

<Note>
  Both are for small, JSON-serialisable values. For project data, write to the project with [`sdk.project`](/sdk-reference#project).
</Note>

### useProject

```ts theme={null}
const { id } = useProject()
```

The open project's id, or null. Follows navigation.

### useNavigate

```ts theme={null}
const navigate = useNavigate()
navigate('/project/abc/environment')   // any app route
navigate.page('settings')              // one of your pages, in the open project
```

### useWindowTitle

```ts theme={null}
useWindowTitle('My Expansion')
```

Sets the window title while the component is mounted.

## Components

**Buttons and inputs**

|                                        |                                                                                                           |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `PrimaryButton`, `SecondaryButton`     | `label` or children, `onClick`, `icon`, `disabled`, `variant` (`default`, `success`, `danger`, `warning`) |
| `IconButton`                           | `icon`, `onClick`, `tooltip`, `size`                                                                      |
| `TextInput`, `TextArea`, `NumberInput` | `value`, `onChange(value)`, `placeholder`, `disabled`                                                     |
| `Checkbox`                             | `checked`, `onChange`                                                                                     |
| `Select`                               | `value`, `onChange`, `options`                                                                            |
| `Slider`                               | `value`, `onChange`, `min`, `max`, `step`                                                                 |

**Layout and containers**

|                                             |                           |
| ------------------------------------------- | ------------------------- |
| `PageTitle`, `SectionTitle`, `Label`        | Headings and field labels |
| `Section`, `Card`, `InfoCard`               | Grouping                  |
| `Row`, `FlexRow`, `Grid`, `ScrollContainer` | Arrangement               |

**Other**

|         |                                                                           |
| ------- | ------------------------------------------------------------------------- |
| `Model` | The app's 3D model viewer                                                 |
| `Icons` | Every [Lucide](https://lucide.dev/icons) icon: `<Icons.Star size={16} />` |

Components take the props the app uses for them; `onChange` on inputs receives the **value**, not the event.

## Styling

Import a CSS file and it is scoped to your expansion, so plain element selectors are safe:

```tsx theme={null}
import './Page.css'                    // .card { … } applies only inside your UI
import styles from './Page.module.css' // styles.card, hashed
```

See [Modules and imports](/modules#css). To restyle the app itself, use [`sdk.theme`](/sdk-reference#theme).

## Errors

If one of your components throws while rendering, only that spot is replaced, by a card naming the file and line, with a Retry button. The rest of the app carries on. See [Lifecycle](/lifecycle).
