Skip to content
Highlight

V2 componentsUpdated Oct 14, 2026

Item

A settings row: what it is, what it means, and the one control that changes it. Use it for any title + description + trailing control, stacked in an Item.Group with hairlines between. Not a Cell, which is a raised card you press, and not a Field, which wires a label to an input: when the control is an input use a justified Field, which shares Item’s type and sits in the same group.

Source: packages/ui/src/components/item

tsx
import { Item, itemVariants, itemTitleClasses, itemDescriptionVariants } from '@highlight/ui'

Change the props; the preview and the snippet update together.

Export dataCopy this workspace’s data to files or to another workspace.
Props
Variant
Size
Media
Description as error
Action
Disabled
tsx
<Item>  <Item.Content>    <Item.Title>Export data</Item.Title>    <Item.Description>      Copy this workspace’s data to files or to another workspace.    </Item.Description>  </Item.Content>  <Item.Actions><Button variant="secondary" size="sm">Export</Button></Item.Actions></Item>

Content fills; Actions sizes to itself at the far end. The title truncates, the description clamps to two lines.

Export dataCopy this workspace’s data to files or to another workspace.
tsx
import { Button, Item } from '@highlight/ui' export default function ItemBasic() {  return (    <Item className="max-w-xl">      <Item.Content>        <Item.Title>Export data</Item.Title>        <Item.Description>Copy this workspace’s data to files or to another workspace.</Item.Description>      </Item.Content>      <Item.Actions>        <Button variant="secondary" size="sm">          Export        </Button>      </Item.Actions>    </Item>  )}

Item.Group draws the hairline between rows and sets their density. Items pad themselves; a justified Field in the same group takes the group’s padding so it lines up.

Single sign-onMembers sign in through your identity provider.
Directory syncPeople removed from your identity provider are removed from this workspace.
tsx
import { ExternalLink } from 'lucide-react'import { Button, Item } from '@highlight/ui' export default function ItemGroup() {  return (    <Item.Group className="max-w-xl">      <Item>        <Item.Content>          <Item.Title>Single sign-on</Item.Title>          <Item.Description>Members sign in through your identity provider.</Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="secondary" size="sm" right={<ExternalLink />}>            Configure          </Button>        </Item.Actions>      </Item>      <Item>        <Item.Content>          <Item.Title>Directory sync</Item.Title>          <Item.Description>            People removed from your identity provider are removed from this workspace.          </Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="secondary" size="sm" right={<ExternalLink />}>            Set up          </Button>        </Item.Actions>      </Item>    </Item.Group>  )}

separator={false} drops the hairlines. For a list the eye already parses row by row, such as one with media tiles, the lines are noise. Pair it with size="sm" for a denser list.

Weekly syncRecorded 42 min ago
Design review notesEdited yesterday
Q4 planningTomorrow, 10:00
tsx
import { Calendar, FileText, Mic } from 'lucide-react'import { Button, Item } from '@highlight/ui' export default function ItemNoSeparator() {  return (    <Item.Group separator={false} size="sm" className="max-w-xl">      <Item>        <Item.Media variant="icon">          <Mic />        </Item.Media>        <Item.Content>          <Item.Title>Weekly sync</Item.Title>          <Item.Description>Recorded 42 min ago</Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="ghost" size="sm">            Open          </Button>        </Item.Actions>      </Item>      <Item>        <Item.Media variant="icon">          <FileText />        </Item.Media>        <Item.Content>          <Item.Title>Design review notes</Item.Title>          <Item.Description>Edited yesterday</Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="ghost" size="sm">            Open          </Button>        </Item.Actions>      </Item>      <Item>        <Item.Media variant="icon">          <Calendar />        </Item.Media>        <Item.Content>          <Item.Title>Q4 planning</Item.Title>          <Item.Description>Tomorrow, 10:00</Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="ghost" size="sm">            Open          </Button>        </Item.Actions>      </Item>    </Item.Group>  )}

image is a bordered tile for a brand mark, icon a quiet tile for a lucide glyph, avatar a circle for a person. Media is aria-hidden: the title names the row.

Google WorkspaceApprove Highlight for your whole company.
AshbyOne API key signs in every member.
Maya ChenAdmin
tsx
import { Building2, Plus } from 'lucide-react'import { Button, Item } from '@highlight/ui' export default function ItemMedia() {  return (    <Item.Group className="max-w-xl">      <Item>        <Item.Media variant="image">          <Building2 />        </Item.Media>        <Item.Content>          <Item.Title>Google Workspace</Item.Title>          <Item.Description>Approve Highlight for your whole company.</Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="outline" shape="square" aria-label="Connect Google Workspace">            <Plus />          </Button>        </Item.Actions>      </Item>      <Item>        <Item.Media variant="icon">          <Building2 />        </Item.Media>        <Item.Content>          <Item.Title>Ashby</Item.Title>          <Item.Description>One API key signs in every member.</Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="outline" shape="square" aria-label="Connect Ashby">            <Plus />          </Button>        </Item.Actions>      </Item>      <Item>        <Item.Media variant="avatar">          <img alt="" src="https://i.pravatar.cc/64?img=12" />        </Item.Media>        <Item.Content>          <Item.Title>Maya Chen</Item.Title>          <Item.Description>Admin</Item.Description>        </Item.Content>      </Item>    </Item.Group>  )}

A justified Field in the same group. Field.Label and Field.Note are built from Item’s title and description styles, so the two rows read as one list.

LogoSquare PNG or SVG, at least 256 × 256 px.

Shown to members at sign-in.

tsx
import { Button, Field, Input, Item } from '@highlight/ui' export default function ItemWithField() {  return (    <Item.Group className="max-w-xl">      <Item>        <Item.Content>          <Item.Title>Logo</Item.Title>          <Item.Description>Square PNG or SVG, at least 256 × 256 px.</Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="secondary" size="sm">            Upload          </Button>        </Item.Actions>      </Item>      <Field orientation="justified">        <Field.Label>Workspace name</Field.Label>        <Field.Note>Shown to members at sign-in.</Field.Note>        <Input defaultValue="Maya’s workspace" className="w-60" />      </Field>    </Item.Group>  )}

disabled greys the text and leaves the control to carry its own disabled. Description tone="danger" is the error in place of the explanation, announced, with no layout shift.

Leave Maya’s workspaceYou’re the only one here. Deactivate it instead.
Deactivate Maya’s workspaceEveryone loses access. Data is deleted after 30 days.
Google WorkspaceNot verifiedFinish authorizing Highlight in the Google Admin console.
tsx
import { Badge, Button, Item } from '@highlight/ui' export default function ItemStates() {  return (    <Item.Group className="max-w-xl">      <Item disabled>        <Item.Content>          <Item.Title>Leave Maya’s workspace</Item.Title>          <Item.Description>You’re the only one here. Deactivate it instead.</Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="secondary" tone="danger" size="sm" disabled>            Leave workspace          </Button>        </Item.Actions>      </Item>      <Item>        <Item.Content>          <Item.Title>Deactivate Maya’s workspace</Item.Title>          <Item.Description>Everyone loses access. Data is deleted after 30 days.</Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="secondary" tone="danger" size="sm">            Deactivate          </Button>        </Item.Actions>      </Item>      <Item>        <Item.Content>          <Item.Title badge={<Badge tone="danger">Not verified</Badge>}>Google Workspace</Item.Title>          <Item.Description tone="danger">Finish authorizing Highlight in the Google Admin console.</Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="secondary" size="sm">            Verify          </Button>        </Item.Actions>      </Item>    </Item.Group>  )}

default is flat for lists. outline and muted give a lone row its own edge or wash.

OutlineIts own edge, for a row that stands alone.
MutedA quiet wash, for a row that is informational.
SmallTighter padding for dense lists.
tsx
import { Button, Item } from '@highlight/ui' export default function ItemVariants() {  return (    <div className="flex max-w-xl flex-col gap-4">      <Item variant="outline">        <Item.Content>          <Item.Title>Outline</Item.Title>          <Item.Description>Its own edge, for a row that stands alone.</Item.Description>        </Item.Content>        <Item.Actions>          <Button variant="ghost" size="sm">            Action          </Button>        </Item.Actions>      </Item>      <Item variant="muted">        <Item.Content>          <Item.Title>Muted</Item.Title>          <Item.Description>A quiet wash, for a row that is informational.</Item.Description>        </Item.Content>      </Item>      <Item size="sm" variant="outline">        <Item.Content>          <Item.Title>Small</Item.Title>          <Item.Description>Tighter padding for dense lists.</Item.Description>        </Item.Content>      </Item>    </div>  )}

The root, then any compound sub-components. Every part accepts className and forwards the rest of its props to the element or Base UI part beneath it.

Item
A div holding one row: Item.Content beside Item.Actions. variant and size set its look.
Item.Group
Stacks rows with a hairline between each; accepts Items and justified Fields. size sets the density of every Item in it; separator={false} drops the hairlines.
Item.Media
Leading tile. variant: icon | image | avatar.
Item.Content
The elastic middle holding Title and Description.
Item.Title
One line, truncates. badge puts a Badge after the text.
Item.Description
Two lines, clamped. tone="danger" turns it into the announced error.
Item.Actions
Trailing controls, sized to themselves.

Props added on top of the underlying element or Base UI part.

PropTypeDefaultDescription
variant'default'|'outline'|'muted''default'default is flat, for rows in a group. outline draws a border, muted a subtle wash, for a row on its own.
size'md'|'sm''md'Vertical padding and gap: 12 / 8. Inherits from a surrounding Item.Group when omitted.
align'center'|'start''center'start pins media and actions to the first line when the description runs long beside a tall control.
renderReactElement—The element to render as, e.g. <a href> or <button>. A rendered row is interactive: hover wash and focus ring.
disabledbooleanfalseGreys the title and description.
  • itemTitleClasses and itemDescriptionVariants are the type Field.Label and Field.Note are built from. Import them for a one-off row rather than restating the classes.
  • Media is aria-hidden by default. Pass aria-hidden={false} and an aria-label when the picture carries something the title does not.