snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
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

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
---
title: AlertDialog
description: Ask for confirmation before an action that needs an explicit decision.
---
import { StoryEmbed } from '/snippets/ui/StoryEmbed.mdx';
import AlertDialogReference from '/snippets/ui/generated/surfaces/alert-dialog.mdx';
Use `AlertDialog` for a decision that interrupts the current workflow, such as confirming deletion. It provides an accessible modal with a backdrop and focus management.
<StoryEmbed
storyId="ui-surfaces-alertdialog--documentation"
title="AlertDialog example"
height={440}
/>
## Anatomy
Keep the trigger and popup inside `AlertDialog.Root`. This tree shows the recommended layout for a confirmation dialog.
```text
AlertDialog.Root
├── AlertDialog.Trigger
└── AlertDialog.Popup
├── AlertDialog.Header
│ ├── AlertDialog.Title
│ └── AlertDialog.Description
├── AlertDialog.Body
└── AlertDialog.Footer
├── AlertDialog.Close
└── Confirmation action
```
| Part | Requirement | Purpose |
| -------------------------- | --------------------------------------------- | ------------------------------------------------------------------------ |
| `Root` | Required | Coordinates open state and modal behavior. |
| `Trigger` | Optional for a programmatically opened dialog | Opens the dialog and provides a return focus target. |
| `Popup` | Required | Renders the modal content with focus management. |
| `Title` | Recommended | Supplies the visible dialog name. An accessible name is always required. |
| `Description` | Recommended for a confirmation | Explains the decision and its consequences. |
| `Header`, `Body`, `Footer` | Optional layout parts | Arrange headings, additional content, and actions. |
| `Close` | Recommended for cancellation | Supplies an explicit close action. |
`Popup` includes the portal, backdrop, and viewport. Put content directly inside it and set `size`, `container`, and focus props on `Popup`. `Header`, `Body`, and `Footer` arrange content; `Title` and `Description` provide the accessible relationships.
The confirmation action is application content, not an exported part. Use `Close` with a synchronous handler or a `Button` with controlled open state for asynchronous confirmation.
## Uncontrolled state
Use `defaultOpen` to set the initial visibility. The dialog owns subsequent changes from its trigger, close controls, and dismissal interactions.
```tsx
import { AlertDialog } from 'twenty-ui/primitives/surfaces';
type DeleteConfirmationProps = { onDelete: () => void };
export const DeleteConfirmation = ({ onDelete }: DeleteConfirmationProps) => (
<AlertDialog.Root defaultOpen={false}>
<AlertDialog.Trigger>Delete record</AlertDialog.Trigger>
<AlertDialog.Popup>
<AlertDialog.Header>
<AlertDialog.Title>Delete this record?</AlertDialog.Title>
<AlertDialog.Description>
This action cannot be undone.
</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Close>Cancel</AlertDialog.Close>
<AlertDialog.Close onClick={onDelete}>Delete</AlertDialog.Close>
</AlertDialog.Footer>
</AlertDialog.Popup>
</AlertDialog.Root>
);
```
`defaultOpen` defaults to `false`, so it can be omitted. This example uses a synchronous confirmation callback and closes after the action runs.
## Controlled state
Pass `open` and update it with `onOpenChange` to own the same dialog's visibility in React state. The callback receives the next boolean and event details, including changes requested by the trigger and close controls.
```tsx
import { useState } from 'react';
import { AlertDialog } from 'twenty-ui/primitives/surfaces';
type DeleteConfirmationProps = { onDelete: () => void };
export const DeleteConfirmation = ({ onDelete }: DeleteConfirmationProps) => {
const [open, setOpen] = useState(false);
return (
<AlertDialog.Root open={open} onOpenChange={setOpen}>
<AlertDialog.Trigger>Delete record</AlertDialog.Trigger>
<AlertDialog.Popup>
<AlertDialog.Header>
<AlertDialog.Title>Delete this record?</AlertDialog.Title>
<AlertDialog.Description>
This action cannot be undone.
</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Close>Cancel</AlertDialog.Close>
<AlertDialog.Close onClick={onDelete}>Delete</AlertDialog.Close>
</AlertDialog.Footer>
</AlertDialog.Popup>
</AlertDialog.Root>
);
};
```
For asynchronous confirmation, use a `Button` with your action handler instead of `AlertDialog.Close`. Keep `open` true while the action is pending, call `setOpen(false)` after it succeeds, and show an error inside the dialog if it fails.
## Compose with buttons
Use `render` to give the trigger and close controls Twenty UI button styling. The dialog parts supply their interaction handlers and refs to the buttons.
```tsx
import { Button } from 'twenty-ui/primitives/input';
import { AlertDialog } from 'twenty-ui/primitives/surfaces';
type DeleteConfirmationProps = { onDelete: () => void };
export const DeleteConfirmation = ({ onDelete }: DeleteConfirmationProps) => (
<AlertDialog.Root>
<AlertDialog.Trigger
render={<Button color="danger">Delete record</Button>}
/>
<AlertDialog.Popup>
<AlertDialog.Header>
<AlertDialog.Title>Delete this record?</AlertDialog.Title>
<AlertDialog.Description>
This action cannot be undone.
</AlertDialog.Description>
</AlertDialog.Header>
<AlertDialog.Footer>
<AlertDialog.Close render={<Button variant="outline">Cancel</Button>} />
<AlertDialog.Close
onClick={onDelete}
render={<Button color="danger">Delete</Button>}
/>
</AlertDialog.Footer>
</AlertDialog.Popup>
</AlertDialog.Root>
);
```
Keep each action as a single interactive control. If you wrap `Button` in your own component, [forward the supplied props and ref](/ui/primitives/overview#custom-components).
## Sizing and portals
`Popup.size` defaults to `md`; see the reference for supported sizes. Use `container` to override the theme's portal container and `keepMounted` to retain mounted content when closed.
## Focus and dismissal
The dialog traps focus while open. Escape can dismiss it; clicking the backdrop does not. Always provide a visible Cancel or Close action. Keep the initial focus on the least destructive action, and preserve a meaningful return focus target when the action removes its trigger. Use `Popup.initialFocus` and `Popup.finalFocus` to customize focus behavior.
## Props
The reference is generated from the public component types. Native attributes, including accessible names and event handlers, are also accepted on parts that render elements.
<AlertDialogReference />

View File

@@ -0,0 +1,39 @@
---
title: Dialog
description: Give a modal a visible heading and accessible name with Dialog.Title.
---
import { StoryEmbed } from '/snippets/ui/StoryEmbed.mdx';
import DialogReference from '/snippets/ui/generated/surfaces/dialog.mdx';
Use `Dialog.Title` inside Twenty UI's existing `Modal`. It provides the shared modal heading style and automatically connects the heading to the modal's accessible name. The public `Dialog` namespace currently exports `Title`.
<StoryEmbed
storyId="ui-surfaces-dialog--documentation"
title="Dialog title example"
height={300}
/>
## Add a modal title
```tsx
import { Dialog, Modal } from 'twenty-ui/primitives/surfaces';
import { Text } from 'twenty-ui/primitives/typography';
type CreditModalProps = { isOpen: boolean };
export const CreditModal = ({ isOpen }: CreditModalProps) => (
<Modal isOpen={isOpen} padding="large" autoHeight>
<Dialog.Title>Grant credits</Dialog.Title>
<Text>Add credits to this workspace.</Text>
</Modal>
);
```
The title defaults to an `h2` with large type, primary text color, centered alignment, and spacing-4 below it. Use `level` to change the document outline independently of `size`. Native `className` and `style` can adjust alignment and spacing.
Native attributes, refs, and `render` are forwarded to the heading. Keep a heading element when using `render`, and keep the title inside `Modal` so its accessible naming relationship is available.
## Props
<DialogReference />

View File

@@ -0,0 +1,235 @@
---
title: Menu
description: Present commands, checked options, radio choices, and nested menus.
---
import { StoryEmbed } from '/snippets/ui/StoryEmbed.mdx';
import MenuReference from '/snippets/ui/generated/surfaces/menu.mdx';
Use `Menu` for actions opened from a trigger. Its items supply keyboard navigation and the visual layout of [ListItem](/ui/primitives/navigation/list-item).
<StoryEmbed
storyId="ui-surfaces-menu--documentation"
title="Menu example"
height={440}
/>
## Anatomy
Keep the trigger and popup inside the same `Menu.Root`. Put action or selection items inside the popup.
```text
Menu.Root
├── Menu.Trigger
└── Menu.Popup
├── Menu.Item
├── Menu.Separator
└── Menu.Item
```
| Part | Requirement | Purpose |
| ------------------------------- | --------------------------------- | -------------------------------------------------------------------------- |
| `Root` | Required | Owns open state and coordinates menu interaction. |
| `Trigger` | Required for a trigger-based menu | Opens the menu and provides its accessible name. |
| `Popup` | Required | Renders the positioned, portaled menu with keyboard navigation. |
| `Item` | One per action | Runs a command when selected. |
| `CheckboxItem` | Optional | Toggles an independent boolean option. |
| `RadioGroup`, `RadioItem` | Optional | Coordinate mutually exclusive options. Put radio items inside their group. |
| `Group`, `GroupLabel` | Optional | Group related items under a label. |
| `Separator` | Optional | Separates groups visually. |
| `SubmenuRoot`, `SubmenuTrigger` | Optional | Add a nested menu with its own popup. |
`Popup` includes the portal and positioner. Menu items include the row layout and selection indicators. Supply labels as children and use `startIcon`, `endIcon`, and `description` for supporting content.
## Uncontrolled state
Use `defaultOpen` to set the initial visibility. The menu owns subsequent changes from its trigger, item selection, and dismissal interactions.
```tsx
import { IconCopy, IconTrash } from 'twenty-ui/icon';
import { Menu } from 'twenty-ui/primitives/surfaces';
type RecordMenuProps = { onDuplicate: () => void; onDelete: () => void };
export const RecordMenu = ({ onDuplicate, onDelete }: RecordMenuProps) => (
<Menu.Root defaultOpen={false}>
<Menu.Trigger>Record actions</Menu.Trigger>
<Menu.Popup>
<Menu.Item startIcon={<IconCopy />} onClick={onDuplicate}>
Duplicate
</Menu.Item>
<Menu.Separator />
<Menu.Item startIcon={<IconTrash />} color="danger" onClick={onDelete}>
Delete
</Menu.Item>
</Menu.Popup>
</Menu.Root>
);
```
`defaultOpen` defaults to `false`, so it can be omitted. Action items close the menu when selected by default. `disabled` prevents selection. `hotkeys` displays shortcut hints; register shortcut handlers in your application.
## Controlled state
Pass `open` and update it with `onOpenChange` to own the same menu's visibility in React state. The callback receives the next boolean and event details, including dismissal and item selection.
```tsx
import { useState } from 'react';
import { IconCopy, IconTrash } from 'twenty-ui/icon';
import { Menu } from 'twenty-ui/primitives/surfaces';
type RecordMenuProps = { onDuplicate: () => void; onDelete: () => void };
export const RecordMenu = ({ onDuplicate, onDelete }: RecordMenuProps) => {
const [open, setOpen] = useState(false);
return (
<Menu.Root open={open} onOpenChange={setOpen}>
<Menu.Trigger>Record actions</Menu.Trigger>
<Menu.Popup>
<Menu.Item startIcon={<IconCopy />} onClick={onDuplicate}>
Duplicate
</Menu.Item>
<Menu.Separator />
<Menu.Item startIcon={<IconTrash />} color="danger" onClick={onDelete}>
Delete
</Menu.Item>
</Menu.Popup>
</Menu.Root>
);
};
```
## Selection and submenus
Item selection is independent of popup visibility. Checkbox items use `defaultChecked` or `checked` with `onCheckedChange`. Radio groups use `defaultValue` or `value` with `onValueChange`.
For a submenu, place `SubmenuRoot` inside the parent popup, with its own trigger and popup. Each nested popup retains its own positioning and keyboard behavior.
```text
Menu.Popup
└── Menu.SubmenuRoot
├── Menu.SubmenuTrigger
└── Menu.Popup
└── Menu.RadioGroup
├── Menu.RadioItem
└── Menu.RadioItem
```
### Uncontrolled item state
Each checkbox item and radio group owns its selection. Both the menu and submenu also manage their own visibility.
```tsx
import { Menu } from 'twenty-ui/primitives/surfaces';
export const ViewMenu = () => (
<Menu.Root>
<Menu.Trigger>View options</Menu.Trigger>
<Menu.Popup>
<Menu.CheckboxItem defaultChecked={false}>
Show archived records
</Menu.CheckboxItem>
<Menu.Separator />
<Menu.SubmenuRoot>
<Menu.SubmenuTrigger>Layout</Menu.SubmenuTrigger>
<Menu.Popup>
<Menu.RadioGroup defaultValue="list">
<Menu.RadioItem value="list">List</Menu.RadioItem>
<Menu.RadioItem value="board">Board</Menu.RadioItem>
</Menu.RadioGroup>
</Menu.Popup>
</Menu.SubmenuRoot>
</Menu.Popup>
</Menu.Root>
);
```
### Controlled item state
The application owns the same selections in React state. Popup visibility remains uncontrolled because neither root receives `open`.
```tsx
import { useState } from 'react';
import { Menu } from 'twenty-ui/primitives/surfaces';
export const ViewMenu = () => {
const [showArchived, setShowArchived] = useState(false);
const [view, setView] = useState('list');
return (
<Menu.Root>
<Menu.Trigger>View options</Menu.Trigger>
<Menu.Popup>
<Menu.CheckboxItem
checked={showArchived}
onCheckedChange={setShowArchived}
>
Show archived records
</Menu.CheckboxItem>
<Menu.Separator />
<Menu.SubmenuRoot>
<Menu.SubmenuTrigger>Layout</Menu.SubmenuTrigger>
<Menu.Popup>
<Menu.RadioGroup value={view} onValueChange={setView}>
<Menu.RadioItem value="list">List</Menu.RadioItem>
<Menu.RadioItem value="board">Board</Menu.RadioItem>
</Menu.RadioGroup>
</Menu.Popup>
</Menu.SubmenuRoot>
</Menu.Popup>
</Menu.Root>
);
};
```
Checkbox and radio items keep the popup open by default so users can adjust options. Use `closeOnClick` to change that behavior. Nest a `SubmenuRoot`, `SubmenuTrigger`, and `Popup` inside a parent popup for submenus. Each `SubmenuRoot` has its own `defaultOpen`, `open`, and `onOpenChange` props, independent of the parent menu.
## Compose a trigger
Use `render` to make a Twenty UI `Button` the trigger. The menu supplies the button's activation handlers and accessibility attributes.
```tsx
import { IconCopy, IconTrash } from 'twenty-ui/icon';
import { Button } from 'twenty-ui/primitives/input';
import { Menu } from 'twenty-ui/primitives/surfaces';
type RecordMenuProps = { onDuplicate: () => void; onDelete: () => void };
export const RecordMenu = ({ onDuplicate, onDelete }: RecordMenuProps) => (
<Menu.Root>
<Menu.Trigger render={<Button variant="outline">Record actions</Button>} />
<Menu.Popup>
<Menu.Item startIcon={<IconCopy />} onClick={onDuplicate}>
Duplicate
</Menu.Item>
<Menu.Separator />
<Menu.Item startIcon={<IconTrash />} color="danger" onClick={onDelete}>
Delete
</Menu.Item>
</Menu.Popup>
</Menu.Root>
);
```
Put `Button` in the trigger's `render` prop to preserve a single interactive control. For custom wrapper components, [forward the supplied props and ref](/ui/primitives/overview#custom-components).
## Keyboard and placement
Arrow keys move through the items, including disabled ones, which receive focus but cannot be selected; typeahead finds items by their text. Enter or Space selects an item. Escape closes the menu and returns focus to its trigger. Use clear text labels even when items have icons.
`Popup` owns its portal and accepts placement props and a `container` override. Placement defaults depend on whether it is a top-level menu or a submenu:
| Popup | `side` | `align` | `sideOffset` | `alignOffset` |
| -------------- | ------------ | ------- | ------------ | ------------- |
| Top-level menu | `bottom` | `start` | `8` | `0` |
| Submenu | `inline-end` | `start` | `0` | `-4` |
It uses the [theme's portal container](/ui/theming) by default.
## Props
The reference is generated from the public component types. Native attributes, including accessible names and event handlers, are also accepted on parts that render elements.
<MenuReference />

View File

@@ -0,0 +1,127 @@
---
title: Popover
description: Display contextual content anchored to a trigger.
---
import { StoryEmbed } from '/snippets/ui/StoryEmbed.mdx';
import PopoverReference from '/snippets/ui/generated/surfaces/popover.mdx';
Use `Popover` for contextual information or lightweight controls. `Popup` includes positioning and a portal, so you can compose content directly inside it.
<StoryEmbed
storyId="ui-surfaces-popover--documentation"
title="Popover example"
height={440}
/>
## Anatomy
Keep the trigger and popup inside `Popover.Root`. The popup can contain text, form controls, and actions.
```text
Popover.Root
├── Popover.Trigger
└── Popover.Popup
├── Popover.Title
├── Popover.Description
└── Popover.Close
```
| Part | Requirement | Purpose |
| ------------- | --------------------------------------- | --------------------------------------------------------------------- |
| `Root` | Required | Coordinates open state and interactions. |
| `Trigger` | Required for a trigger-anchored popover | Opens or closes the popup and supplies its anchor. |
| `Popup` | Required | Renders positioned content with focus management. |
| `Title` | Recommended | Supplies a visible popup name. An accessible name is always required. |
| `Description` | Optional | Associates explanatory text with the popup. |
| `Close` | Optional | Provides an explicit close action within the popup. |
`Popup` includes the portal and positioner. Set `arrow` on `Popup` to add its built-in arrow. All parts share their root's context even though the popup renders in a portal; the tree describes component nesting, not the final DOM placement.
## Uncontrolled state
Use `defaultOpen` to set the initial visibility. The popover owns subsequent changes from its trigger, close controls, and dismissal interactions.
```tsx
import { Popover } from 'twenty-ui/primitives/surfaces';
export const ImportHelp = () => (
<Popover.Root defaultOpen={false}>
<Popover.Trigger>Import help</Popover.Trigger>
<Popover.Popup arrow>
<Popover.Title>Import a CSV file</Popover.Title>
<Popover.Description>
Use a header row to identify your columns.
</Popover.Description>
<Popover.Close>Got it</Popover.Close>
</Popover.Popup>
</Popover.Root>
);
```
`defaultOpen` defaults to `false`, so it can be omitted. Use `Title` and `Description` to identify the popup. The trigger renders a button by default; `render` lets you supply a compatible element.
## Controlled state
Pass `open` and update it with `onOpenChange` to own the same popover's visibility in React state.
```tsx
import { useState } from 'react';
import { Popover } from 'twenty-ui/primitives/surfaces';
export const ImportHelp = () => {
const [open, setOpen] = useState(false);
return (
<Popover.Root open={open} onOpenChange={setOpen}>
<Popover.Trigger>Import help</Popover.Trigger>
<Popover.Popup arrow>
<Popover.Title>Import a CSV file</Popover.Title>
<Popover.Description>
Use a header row to identify your columns.
</Popover.Description>
<Popover.Close>Got it</Popover.Close>
</Popover.Popup>
</Popover.Root>
);
};
```
`onOpenChange` receives the next boolean and event details, including changes requested by the trigger, close controls, Escape, and outside interaction.
## Compose with buttons
Pass a Twenty UI `Button` through `render` to use it as the trigger or close control. The popover keeps its open state, keyboard behavior, and focus handling.
```tsx
import { Button } from 'twenty-ui/primitives/input';
import { Popover } from 'twenty-ui/primitives/surfaces';
export const ImportHelp = () => (
<Popover.Root>
<Popover.Trigger render={<Button variant="outline">Import help</Button>} />
<Popover.Popup arrow>
<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>
);
```
`render` composes behavior onto the supplied button. Custom wrapper components must [forward the supplied props and ref](/ui/primitives/overview#custom-components) to preserve that behavior.
## Placement and focus
`Popup` defaults to `side="bottom"`, `align="center"`, and `sideOffset={8}`. Set `arrow` to show an arrow. Set `container` to override the theme's portal container; see [theming](/ui/theming).
Escape closes the popup and focus returns to the trigger. Outside interaction dismisses it by default. Use the focus props on `Popup` when its content needs a specific initial or return focus target.
## Props
The reference is generated from the public component types. Native attributes, including accessible names and event handlers, are also accepted on parts that render elements.
<PopoverReference />

View File

@@ -0,0 +1,125 @@
---
title: Tooltip
description: Show supplementary information when a trigger is hovered or focused.
---
import { StoryEmbed } from '/snippets/ui/StoryEmbed.mdx';
import TooltipReference from '/snippets/ui/generated/surfaces/tooltip.mdx';
Use `Tooltip` for a short explanation of a control or truncated text. Keep essential information visible, give controls an accessible name, and use [Popover](/ui/primitives/surfaces/popover) when the content contains links, buttons, or other controls.
<StoryEmbed storyId="ui-surfaces-tooltip--default" title="Tooltip example" />
## Single trigger
```tsx
import { Button } from 'twenty-ui/primitives/input';
import { Tooltip } from 'twenty-ui/primitives/surfaces';
export const ExportHelp = () => (
<Tooltip content="Download the visible records as a CSV file" side="top">
<span>
<Button>Export records</Button>
</span>
</Tooltip>
);
```
The convenience component accepts one trigger element as `children`. Native elements receive the trigger attributes, event handlers, and ref directly. Custom trigger components must forward all of them. A wrapper element also works with a component that does not forward every trigger prop.
Hover opens the tooltip after `delay` milliseconds, which defaults to `600`. Keyboard focus opens it immediately. `closeDelay` defaults to `0`; hovering over the popup keeps it open unless `disableHoverablePopup` is set. Escape dismisses it.
## Controlled state
Use `open` and `onOpenChange` together when the application owns visibility. Use `defaultOpen` only to set the initial visibility of an uncontrolled tooltip.
```tsx
import { useState } from 'react';
import { Button } from 'twenty-ui/primitives/input';
import { Tooltip } from 'twenty-ui/primitives/surfaces';
export const ControlledExportHelp = () => {
const [open, setOpen] = useState(false);
return (
<Tooltip
content="Download the visible records as a CSV file"
open={open}
onOpenChange={setOpen}
delay={300}
>
<span>
<Button>Export records</Button>
</span>
</Tooltip>
);
};
```
## Rich content and composition
`Tooltip.Content` provides a title, optional description, and optional leading icon. Use it as `content` or inside `Tooltip.Popup`.
```tsx
import { IconInfoCircle } from 'twenty-ui/icon';
import { Button } from 'twenty-ui/primitives/input';
import { Tooltip } from 'twenty-ui/primitives/surfaces';
export const ExportDetails = () => (
<Tooltip.Root>
<Tooltip.Trigger render={<span />}>
<Button>Export records</Button>
</Tooltip.Trigger>
<Tooltip.Popup side="right" align="start" arrow>
<Tooltip.Content
startIcon={<IconInfoCircle />}
description="Only the records matching your current filters are included."
>
Export visible records
</Tooltip.Content>
</Tooltip.Popup>
</Tooltip.Root>
);
```
| Part | Purpose |
| ---------- | ------------------------------------------------------------- |
| `Root` | Coordinates visibility and trigger interactions. |
| `Trigger` | Renders the element that opens the tooltip on hover or focus. |
| `Popup` | Renders positioned content in a portal. |
| `Content` | Styles primary text, a description, and an icon. |
| `Provider` | Coordinates opening delays across a group of tooltips. |
`Tooltip.createHandle()` connects detached triggers to a shared `Root` through their `handle` prop. Keep each popup's content supplementary even when several triggers share it.
## Disabled controls
Native disabled buttons cannot receive keyboard focus. Wrap one in a focusable trigger so users can discover why the action is unavailable.
```tsx
import { Button } from 'twenty-ui/primitives/input';
import { Tooltip } from 'twenty-ui/primitives/surfaces';
export const UnavailableExport = () => (
<Tooltip content="Select at least one record to export">
<span tabIndex={0} aria-label="Export records unavailable">
<Button disabled>Export records</Button>
</span>
</Tooltip>
);
```
Setting `disabled` on `Tooltip` suppresses the tooltip itself. It does not disable the control.
## Placement and overflowing text
The popup defaults to `side="top"`, `align="center"`, `sideOffset={10}`, and a maximum width of `300` pixels. Placement adjusts to available space. Use `arrow` for an arrow and `container` to override the theme's portal destination; see [theming](/ui/theming).
Use `OverflowingTextWithTooltip` when a text label should show its full value only when truncated. Its existing `text`, `tooltipContent`, and `tooltipPlace` props remain available.
## Props
The reference is generated from the public component types. Native popup attributes and event handlers are also accepted.
<TooltipReference />