Skip to content

ComposerChip

Hero example

When to use

Reach for ComposerChip when you need an action affordance inside the Composer bottom-row chip slot — typical uses are an attach button (<ComposerChip icon="paperclip">Attach</ComposerChip>), a model picker (Model · claude-3-7-sonnet), or a context selector (Context · this entity). Chips are real <button> elements: clicking opens a picker, triggers an action, or submits a form (type="submit").

Don't reach for ComposerChip outside the Composer surface — the chip's geometry, focus ring, and hover state are tuned to the glass-card backdrop. For inline tags use Pill; for keyed identifiers use MonoChip.

Do — render ComposerChip instances as the chips prop value on Composer. The slot handles layout.
Don't — render ComposerChip standalone outside Composer. The styling assumes the glass-card backdrop.

Anatomy

A single <button class="composer-chip"> with two slots:

  1. Icon slot. Optional icon prop. Rendered before children. Pass a Lucide icon component or any ReactNode.
  2. Children. The chip label, typically a single line. Mono font, 12px, 0.04em letter-spacing.

The button reads its colors and hover lift from the .composer-chip recipe in @matter/components/styles.css. There are no chip variants — the styling is fixed; differentiation comes from icon + label content.

Props

PropTypeDefaultDescription
iconReactNode
childrenrequiredReactNode
onClick() => void
type"button" | "submit""button"

The type prop defaults to "button". Pass "submit" only if the chip should submit the surrounding form (rare — the Composer's send button does this).

States

StateTriggerVisual
DefaultInitial mountResting chip — glass-card background, muted foreground
HoverMouse hoverBackground lifts to --ink-6, foreground brightens to --fg
Focus-visibleKeyboard focusPeach focus ring (--focus-ring)
ActiveMouse down or :activeBackground drops to --ink-12
Presseddata-pressed (consumer-managed)Persistent active appearance — for toggle chips

Density

Chips inherit the Composer's fixed density (always comfortable). The site-level density toggle does not affect Composer chips.

Themes

Tokens consumed

No tokens match.
radii2 tokensv2.3.0
999
matter
src ↗
9999
brand
src ↗

Background reads --ink-6--ink-12 on hover/active. Foreground reads --fg-muted--fg on hover. Border radius reads --radius-pill.

Accessibility

  • Keyboard interactions. Real <button> — Enter and Space activate. Tab moves to next chip in slot order; Shift+Tab returns.
  • ARIA roles and properties. Implicit button role. If the chip is icon-only (no children text), the consumer must pass aria-label (Composer's chip slot wraps the chip; pass aria-label directly on <ComposerChip> — it forwards via spread).
  • Focus order. Within the Composer chip slot, chips are in DOM order. Focus moves textarea → chip 1 → chip 2 → … → send button.
  • Screen-reader expectations. Announces as "{icon} (if not aria-hidden), {children}, button." For icon-only chips, the aria-label is the accessible name.
  • Reduced motion. Hover lift collapses to opacity transition under prefers-reduced-motion: reduce.
  • Forced colors. Background → ButtonFace; foreground → ButtonText; border → ButtonBorder.
  • WIG rules. Real <button> (semantic-element rule). touch-action: manipulation on the chip prevents 300ms double-tap delay. Icon-only chips require aria-label (icon-only-button rule).

Do / Don't

Do — keep labels short ("Attach", "Model · gpt-4o"). Chips are constrained to one line.
Don't — pass long labels that wrap. The chip slot grows horizontally; wrapping breaks the layout.
Do — render a Lucide icon component as icon. Use 14px size for visual parity with chip text.
Don't — render an avatar or a graphic asset as icon. The slot expects a simple line icon.
Do — wire onClick to open a picker (model select, attachment picker). The chip is the trigger, not the picker UI.
Don't — toggle state inside the chip without exposing the new value via children. The label should always reflect current state.

Recipes

This component appears in:

Code example

import { Composer, ComposerChip } from "@matter/components";
import { Paperclip, Sliders } from "lucide-react";

export function AssistantInput() {
  return (
    <Composer
      onSubmit={(q) => handleAsk(q)}
      chips={
        <>
          <ComposerChip icon={<Paperclip size={14} />}>Attach</ComposerChip>
          <ComposerChip icon={<Sliders size={14} />} onClick={openModelPicker}>
            Model · claude-3-7-sonnet
          </ComposerChip>
        </>
      }
    />
  );
}

Source

packages/components/src/Composer/Composer

On this page