---
name: alouette-data
description: >
  Show a value the app already holds. Badge labels something with a status,
  count or category pill; Avatar stands for a person or account; EditableItem
  shows one labelled value behind an edit affordance and EditableSurface a whole
  titled section behind one; Bullet is an icon + text list row; Code and
  CodeBlock render source inline and as a block; Blockquote and Citation render
  a quoted excerpt and its attribution. All are display-only — they own no
  editor and no press handling, and are never wrapped in a Link or Pressable to
  gain one. Load when showing a status chip, a list row, an avatar, a value or
  section with an edit affordance, code, a quote or its attribution.
type: core
library: alouette
library_version: "22.11.0"
requires:
  - alouette-theming
  - alouette-actions
sources:
  - "christophehurpeau/alouette:packages/alouette/src/ui/data/Badge.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/data/Badge.stories.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/data/EditableItem.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/data/EditableItem.stories.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/containers/EditableSurface.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/containers/EditableSurface.stories.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/data/Bullet.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/data/Bullet.stories.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/data/Code.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/data/CodeBlock.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/data/Blockquote.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/data/Citation.tsx"
  - "christophehurpeau/alouette:packages/alouette/src/ui/data/Avatar.tsx"
---

This skill builds on alouette-theming. Read it first for the accent model.

# alouette — Data display

`Badge` is a small, self-sizing pill that labels something: a status, a count, a
category. It is display-only — it renders no press handling and no accessible
role, and is never wrapped in a `Link` or `Pressable` to gain one; put the link
beside it.

## Setup

```tsx
import { Badge } from "alouette";

<Badge>New</Badge>;
```

## Core Patterns

### Accent

`accent` defaults to `"brand"` and drives the whole badge — it wraps its content
in `AccentScope`, so the background, border and text all resolve from the accent
theme in the current light/dark mode.

```tsx
<Badge accent="success">Paid</Badge>
<Badge accent="danger">Overdue</Badge>
<Badge accent="info">Beta</Badge>
```

### Variants

`variant` is `"solid" | "solid.enabled" | "outlined"`, default `"solid"`.

```tsx
<Badge variant="solid">Draft</Badge>          {/* tinted fill, sharp text */}
<Badge variant="solid.enabled">Active</Badge> {/* full accent fill, on-accent text */}
<Badge variant="outlined">Archived</Badge>    {/* accent border + accent text */}
```

`solid` is the quiet default (a tinted highlight background). Use
`solid.enabled` for the one badge that must read as switched-on, and `outlined`
where a fill would compete with the surface behind it.

### Size and icon

`size` is `"sm" | "md"` (default `"md"`) and controls padding, text size **and**
the icon size (12px / 16px). Pass `icon` as a rendered element; do not size it
yourself.

```tsx
import { StarRegularIcon } from "alouette-icons/phosphor-icons/StarRegularIcon";

<Badge size="sm" icon={<StarRegularIcon />}>
  Featured
</Badge>;
```

### Placement

The badge is `self-start`, so it never stretches to fill a stack. Position it
with a wrapper — it takes no `className`.

```tsx
<HStack className="gap-xs items-center">
  <Text className="text-base">Invoice #128</Text>
  <Badge accent="success" size="sm">
    Paid
  </Badge>
</HStack>
```

## Avatar

`Avatar` is the accent disc standing for a person or an account: up to two
initials derived from `name`, or an `icon` in their place. `size` is
`"sm" | "md" | "lg"` (28/32/40px), `accent` defaults to `"brand"`, and
`className` is for layout only. Display-only like `Badge` — a pressable avatar is
a `PressableBox` (or an `AppHeaderAccount`, alouette-layout/SKILL.md) wrapped
around one, never the disc given a role of its own.

```tsx
import { Avatar } from "alouette";

<Avatar name="Ada Lovelace" />          {/* AL */}
<Avatar size="lg" accent="info" icon={<UserRegularIcon />} />
```

## Bullet

`Bullet` is one row of an icon-led list: a leading `icon` tinted `text-accent`
and the text as `children`. It takes no `accent` of its own — it reads the
nearest scope, so accent a whole list by accenting its container. Stack rows in a
`VStack` and choose the gap yourself.

```tsx
import { Bullet, VStack } from "alouette";
import { CheckCircleRegularIcon } from "alouette-icons/phosphor-icons/CheckCircleRegularIcon";

<VStack className="gap-xs">
  <Bullet icon={<CheckCircleRegularIcon />}>Consistent UI</Bullet>
  <Bullet icon={<CheckCircleRegularIcon />}>Accessible</Bullet>
</VStack>;
```

The icon stays aligned with the **first** line (`items-start`) and the text
shrinks, so long content wraps under itself rather than pushing the icon down.

## Code and CodeBlock

`Code` is an inline fragment — mono family on the highlight layer, `role="code"`
(a real `<code>` on web). It sets **no** size class, so nested in a `Text` or
`Paragraph` it inherits the surrounding size and never breaks the line rhythm.

```tsx
import { Code, CodeBlock, Paragraph } from "alouette";

<Paragraph>
  Run <Code>pnpm --filter alouette build:css</Code> after editing the palette.
</Paragraph>;
```

`CodeBlock` is the block form: a lowered `Surface` holding mono text that scrolls
horizontally instead of wrapping, with an optional `title` (a file name or a
language) above it and `size` `"sm" | "md"` (default `"md"`) for the code text.

```tsx
<CodeBlock title="theme.ts">{`export const theme = {
  accent: "brand",
};`}</CodeBlock>
```

Both are display-only. To offer copying, put an `IconButton` beside the block —
never wrap it in a pressable.

## Blockquote and Citation

`Blockquote` renders a quoted excerpt behind an accent rule on its leading edge
(`role="blockquote"` → a real `<blockquote>` on web); its `children` are the
quote text and go into a `Paragraph`, sized by `size` `"sm" | "md"`. Attribution
goes to the `citation` prop as a node — normally a `Citation`.

```tsx
import { Blockquote, Citation } from "alouette";

<Blockquote
  accent="brand"
  citation={
    <Citation href="https://example.com/the-left-hand-of-darkness">
      The Left Hand of Darkness
    </Citation>
  }
>
  Light is the left hand of darkness.
</Blockquote>;
```

`Citation` is an em dash plus the source, muted, `size` `"sm" | "md"`. With
`href` the source becomes an `ExternalLinkText` (so `openLinkBehavior` applies);
without it, plain muted text. It stands alone under any excerpt, not only under a
`Blockquote`.

## EditableItem and EditableSurface

Two read-only displays carrying an edit affordance: `EditableItem` for one
labelled value (a bold label, a summary beside it), `EditableSurface` for a
titled section whose value spans several lines. Both own **no** editor and no
state — they call `onEdit`, and the pencil `IconButton` is the only pressable.

```tsx
<EditableItem
  label="Display name"
  summary={<Badge accent="brand">Ada Lovelace</Badge>}
  editAriaLabel="Edit display name"
  onEdit={openEditor}
/>;

<EditableSurface
  title="Event details"
  titleBadge={<Badge accent="brand">12 August 2026</Badge>}
  editAriaLabel="Edit event details"
  onEdit={openEditor}
>
  <Paragraph className="text-sm">An evening of readings…</Paragraph>
</EditableSurface>;
```

When the editor is a modal form, use `FormEditableItem` /
`FormEditableSurface` (alouette-forms/SKILL.md) rather than wiring `onEdit`
yourself. Props, which ones belong to the Surface rather than the button, and
how to choose between the two:
[references/editable-displays.md](references/editable-displays.md).

## Common Mistakes

### HIGH Passing the icon as a component instead of an element

Wrong:

```tsx
<Badge icon={StarRegularIcon}>Featured</Badge>
```

Correct:

```tsx
<Badge icon={<StarRegularIcon />}>Featured</Badge>
```

`icon` is typed `SVGIconElement` — a rendered element, matching `Button` and
`Message`. A component reference is a type error and renders nothing.

Source: packages/alouette/src/ui/data/Badge.tsx; ui/primitives/Icon.tsx

### MEDIUM Passing className to Badge

Wrong:

```tsx
<Badge className="ml-xs bg-blue-500">New</Badge>
```

Correct:

```tsx
<Box className="ml-xs">
  <Badge accent="info">New</Badge>
</Box>
```

`BadgeProps` is `accent | size | variant | icon | children` — there is no
`className` (nor `style`) prop. Appearance comes from `accent` + `variant`;
spacing and placement come from the parent.

Source: packages/alouette/src/ui/data/Badge.tsx

### MEDIUM Inventing variant or size values

Wrong:

```tsx
<Badge variant="filled" size="lg">
  Active
</Badge>
```

Correct:

```tsx
<Badge variant="solid.enabled" size="md">
  Active
</Badge>
```

`variant` is exactly `"solid" | "solid.enabled" | "outlined"` (note the dotted
name) and `size` is only `"sm" | "md"` — unlike `Message`, there is no `lg`.

Source: packages/alouette/src/ui/data/Badge.tsx

### MEDIUM Hand-rolling a pill with Box + Text

Wrong:

```tsx
<Box className="rounded-full bg-green-100 px-xs py-xxs">
  <Text className="text-xs">Paid</Text>
</Box>
```

Correct:

```tsx
<Badge accent="success" size="sm">
  Paid
</Badge>
```

A manual pill uses raw Tailwind colors instead of accent tokens, so it does not
follow light/dark mode or the accent theme, and its padding/radius drift from
the scale.

Source: packages/alouette/src/ui/data/Badge.tsx

### HIGH Wrapping a display-only component in a Link or Pressable

`Badge`, `Bullet` and an `EditableItem` `summary` are display-only; wrapping one
to make it interactive is forbidden — see alouette-styling/SKILL.md for the rule.
On native it is also broken layout: an expo Router `<Link>` without `asChild`
renders a `Text`, which cannot contain the `View` these components render.

Wrong:

```tsx
<Link href={ticket.url}>
  <Badge>{ticket.key}</Badge>
</Link>
```

Correct — the link beside the badge it labels, or inside the `Bullet` text:

```tsx
<HStack className="gap-xs items-center">
  <ExternalLinkText size="sm" href={ticket.url} text={ticket.key} />
  {ticket.status ? <Badge size="sm">{ticket.status}</Badge> : null}
</HStack>

<Bullet icon={<FileRegularIcon />}>
  <ExternalLinkText href={doc.url} text={doc.title} />
</Bullet>
```

When the pressable target must itself be a pill, build the chip with
`PressableBox` (alouette-actions) rather than nesting a `Badge` in a link.

Source: packages/alouette/src/ui/data/Badge.tsx; ui/actions/ExternalLinkText.tsx;
ui/actions/PressableBox.tsx

### HIGH Hand-rolling the label + value + pencil row

Wrong:

```tsx
<HStack className="items-center justify-between">
  <HStack className="items-center gap-sm">
    <Text className="font-body-bold text-md">Display name</Text>
    <Badge>{name}</Badge>
  </HStack>
  <IconButton
    size="sm"
    icon={<PencilSimpleRegularIcon />}
    onPress={openEditor}
  />
</HStack>
```

Correct:

```tsx
<EditableItem
  label="Display name"
  summary={<Badge>{name}</Badge>}
  editAriaLabel="Edit display name"
  onEdit={openEditor}
/>
```

The hand-rolled row drifts on spacing and usually forgets the edit button's
`aria-label`, leaving it unnamed for assistive tech. When the editor is a form
modal, `FormEditableItem` replaces the surrounding state as well.

Source: packages/alouette/src/ui/data/EditableItem.tsx

### MEDIUM Hand-rolling a bulleted row

Wrong:

```tsx
<HStack className="gap-sm items-start">
  <Icon icon={<CheckCircleRegularIcon />} className="text-accent" />
  <Text className="shrink">Accessible</Text>
</HStack>
```

Correct:

```tsx
<Bullet icon={<CheckCircleRegularIcon />}>Accessible</Bullet>
```

That is exactly what `Bullet` renders. Hand-rolled rows drift on the gap and
routinely drop `shrink` on the `Text`, which stops long text from wrapping.

Source: packages/alouette/src/ui/data/Bullet.tsx

### MEDIUM Expecting EditableItem or EditableSurface to open an editor

Neither renders a modal nor holds state — `onEdit` is yours to wire. Nothing
happens on press until you open something from it; the form-modal versions are
`FormEditableItem` / `FormEditableSurface`.

Source: packages/alouette/src/ui/data/EditableItem.tsx;
ui/containers/EditableSurface.tsx; ui/forms/FormEditableItem.tsx

### MEDIUM Pinning a size on inline Code

Wrong:

```tsx
<Paragraph className="text-xl">
  Run <Code className="text-sm">build:css</Code> first.
</Paragraph>
```

Correct:

```tsx
<Paragraph className="text-xl">
  Run <Code>build:css</Code> first.
</Paragraph>
```

`Code` deliberately carries no size class so it inherits the text it sits in; a
fixed size makes the fragment jump out of the line on both platforms.

Source: packages/alouette/src/ui/data/Code.tsx

### MEDIUM Hand-rolling a code block with Surface + Text

Wrong:

```tsx
<Surface variant="lowered">
  <Text className="font-mono text-sm">{snippet}</Text>
</Surface>
```

Correct:

```tsx
<CodeBlock title="theme.ts">{snippet}</CodeBlock>
```

The hand-rolled block wraps long lines instead of scrolling them, and drops the
`select-auto` / `web:whitespace-pre` pair that keeps the code selectable and
its indentation intact on web.

Source: packages/alouette/src/ui/data/CodeBlock.tsx

See also: alouette-icons/SKILL.md for importing icon elements;
alouette-theming/SKILL.md for what each accent resolves to;
alouette-external-links/SKILL.md and alouette-actions/SKILL.md for the link and
pressable components these display-only ones must never stand in for.
