Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s
97 lines
9.9 KiB
Plaintext
97 lines
9.9 KiB
Plaintext
---
|
|
title: Primitives
|
|
sidebarTitle: Overview
|
|
description: Foundational controls for building interfaces with Twenty UI.
|
|
---
|
|
|
|
Primitives are the foundational controls in Twenty UI. They include inputs, display elements, navigation, and compound controls such as Select, Menu, and AlertDialog. Each primitive receives data and callbacks from your application.
|
|
|
|
Import primitives from a family entry point, such as `twenty-ui/primitives/input`. See [Getting started](/ui/getting-started) for installation and theme setup.
|
|
|
|
[Components](/ui/components/overview) combine these primitives into shared design presets and reusable app building blocks.
|
|
|
|
## State ownership
|
|
|
|
Use uncontrolled state when a component can manage its own interactions. A default prop supplies the initial state. Use controlled state when the application needs to coordinate that state with other controls, navigation, or application data.
|
|
|
|
| State | Uncontrolled initial state | Controlled state | Change callback |
|
|
| ------------------------------- | -------------------------- | ---------------- | ----------------- |
|
|
| Input, selection, or active tab | `defaultValue` | `value` | `onValueChange` |
|
|
| Checkbox or switch | `defaultChecked` | `checked` | `onCheckedChange` |
|
|
| Popup visibility | `defaultOpen` | `open` | `onOpenChange` |
|
|
|
|
In controlled mode, pass the current state and update it in the change callback. In uncontrolled mode, you can still listen to changes without taking ownership of the state. Adding a callback alone does not make a component controlled.
|
|
|
|
Choose a mode for each state and keep it for the lifetime of the component. Default props initialize state; changing them later does not replace the current state. Avoid passing both the default and controlled prop for the same state.
|
|
|
|
Some components have more than one independent state. For example, [Select](/ui/primitives/input/select#open-state) can have a controlled value and uncontrolled popup visibility. [Menu](/ui/primitives/surfaces/menu#selection-and-submenus) can manage its own visibility while the application controls checkbox and radio selections.
|
|
|
|
State belongs to the part that manages the interaction. Configure radio selection on [RadioGroup](/ui/primitives/input/radio-group), not on each Radio. Display components such as Text do not have a controlled or uncontrolled mode. [Toast](/ui/primitives/feedback/toast) delegates its visibility and removal to the application.
|
|
|
|
## Composition
|
|
|
|
Compound primitives expose parts such as `Select.Root`, `Select.Trigger`, and `Select.Popup`. Keep related parts under the same root in the component tree so they share state and accessibility relationships. Each component's anatomy section identifies the required parts and optional content.
|
|
|
|
Slots such as `InputGroup.startElement` and `ListItem.actions` accept React nodes as props. They add content within a component's layout. Parts and slots are different APIs; use the names listed in the component guide.
|
|
|
|
### Popup composition
|
|
|
|
Twenty UI packages the supporting overlay elements inside each public `Popup` part:
|
|
|
|
| Component | Included in `Popup` | Configure on `Popup` |
|
|
| ------------- | -------------------------------------- | -------------------------------------------------------- |
|
|
| `Select` | Portal and positioner | Placement, anchor, and portal container |
|
|
| `Menu` | Portal and positioner | Placement, anchor, portal container, and mounting |
|
|
| `Popover` | Portal, positioner, and optional arrow | Placement, anchor, portal container, mounting, and arrow |
|
|
| `AlertDialog` | Portal, backdrop, and viewport | Size, portal container, mounting, and focus |
|
|
|
|
Place content directly inside these popup parts. Their portal may move the rendered DOM to another container while preserving the component hierarchy and context. See [theming](/ui/theming#scope-an-override) for scoped portal containers.
|
|
|
|
### Custom components
|
|
|
|
For parts that support `render`, pass a compatible element or component to replace the default element. For example, `Popover.Trigger` can use a Twenty UI `Button` while retaining its trigger behavior.
|
|
|
|
A custom wrapper must pass the supplied props and ref to the interactive element. This preserves event handlers, accessibility attributes, positioning, and focus restoration. This example forwards them through `Button`, which attaches them to its native button.
|
|
|
|
```tsx
|
|
import { type ComponentPropsWithRef } from 'react';
|
|
import { Button } from 'twenty-ui/primitives/input';
|
|
import { Popover } from 'twenty-ui/primitives/surfaces';
|
|
|
|
type HelpButtonProps = ComponentPropsWithRef<typeof Button>;
|
|
|
|
const HelpButton = ({ ref, ...props }: HelpButtonProps) => (
|
|
<Button {...props} ref={ref} variant="outline" />
|
|
);
|
|
|
|
export const ImportHelp = () => (
|
|
<Popover.Root>
|
|
<Popover.Trigger render={<HelpButton>Import help</HelpButton>} />
|
|
<Popover.Popup>
|
|
<Popover.Title>Import a CSV file</Popover.Title>
|
|
<Popover.Description>
|
|
Use a header row to identify your columns.
|
|
</Popover.Description>
|
|
<Popover.Close render={<Button>Got it</Button>} />
|
|
</Popover.Popup>
|
|
</Popover.Root>
|
|
);
|
|
```
|
|
|
|
Pass the button through `render` and keep each action as a single interactive control. Preserve the handlers and ref supplied to your wrapper when adding your own behavior. The ref must reach the element that receives focus.
|
|
|
|
Use a native button for button-like parts. On parts that expose `nativeButton`, set `nativeButton={false}` only when the rendered element is not a native button. A custom component that ultimately renders a button, such as `HelpButton`, keeps the default button behavior.
|
|
|
|
## Browse primitives
|
|
|
|
Each page includes usage examples, interaction guidance, and a generated API reference.
|
|
|
|
| Family | Primitives |
|
|
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| Data display | [Avatar](/ui/primitives/data-display/avatar), [Chip](/ui/primitives/data-display/chip), [Tag](/ui/primitives/data-display/tag), [Status](/ui/primitives/data-display/status) |
|
|
| Forms | [Button](/ui/primitives/input/button), [ButtonGroup](/ui/primitives/input/button-group), [Field](/ui/primitives/input/field), [Input](/ui/primitives/input/input), [InputGroup](/ui/primitives/input/input-group), [Textarea](/ui/primitives/input/textarea), [Checkbox](/ui/primitives/input/checkbox), [Radio](/ui/primitives/input/radio), [RadioGroup](/ui/primitives/input/radio-group), [Select](/ui/primitives/input/select), [Slider](/ui/primitives/input/slider), [Switch](/ui/primitives/input/switch) |
|
|
| Navigation | [ListItem](/ui/primitives/navigation/list-item), [Tabs](/ui/primitives/navigation/tabs) |
|
|
| Surfaces | [AlertDialog](/ui/primitives/surfaces/alert-dialog), [Dialog](/ui/primitives/surfaces/dialog), [Menu](/ui/primitives/surfaces/menu), [Popover](/ui/primitives/surfaces/popover), [Tooltip](/ui/primitives/surfaces/tooltip) |
|
|
| Feedback | [Toast](/ui/primitives/feedback/toast) |
|
|
| Typography | [Heading](/ui/primitives/typography/heading), [Text](/ui/primitives/typography/text) |
|