Welcome to Oshon · v1.0  ·  Now in public beta for enterprise teams Read the launch notes
AIUpdatedPro· AI packWCAG 2.2 AA

AI Chat

AI chat popup.

FigmaStorybookSource · PronpmPro

Unlock AI Chat

Compact AI chat popup with suggestions, streaming bubble support, and processing states. Part of the AI pack.

AI pack starts at $249/yr Y1 (renews at $174/yr). Or get every Pro pack with Solo for $999/yr.

Preview

Live preview
@oshon-ai/components

Oshon Copilot

How can I help you trace a shipment today?
Oshon may make mistakes. Verify critical decisions.

Installation

Install the runtime packages:

pnpm
pnpm add @oshon-ai/components @oshon-ai/tokens @oshon-ai/primitives

Or scaffold the component source directly into your codebase (shadcn-style):

pnpm
pnpm dlx @oshon-ai/cli add aichat

Wire the tokens into your Tailwind v4 stylesheet:

css
/* app/globals.css */
@import 'tailwindcss';
@import '@oshon-ai/tokens/css';
@import '@oshon-ai/tokens/tailwind';

New here? Walk through the full setup — prereqs, theming, your first render.

Usage

Import the component and render it. Every component supports the standard tier, size, and disabled props where applicable.

tsx
'use client';
import { AIChat } from '@oshon-ai/components';

export default function Example() {
  return <AIChat />;
}
Pro · AI

Compact AI chat popup with suggestions, streaming bubble support, and processing states. Part of the AI pack.

Unlock AI Chat

Compact AI chat popup with suggestions, streaming bubble support, and processing states. Part of the AI pack.

AI pack starts at $249/yr Y1 (renews at $174/yr). Or get every Pro pack with Solo for $999/yr.

API

Every prop is documented here directly from the component's TypeScript interface. Inherited DOM attributes (aria-*, onClick, style, etc.) work as usual but are omitted from this table.

PropTypeDefaultDescription
title*ReactNodeTitle shown in the header (e.g. "FSMA 204 Answers").
alwaysShowSuggestionsbooleanKeep the suggestion rail visible even after a conversation has started.
aria-labelstringAI chatAccessible label for the root container. Default `'AI chat'`.
Pro · AI

Compact AI chat popup with suggestions, streaming bubble support, and processing states. Part of the AI pack.

Unlock AI Chat

Compact AI chat popup with suggestions, streaming bubble support, and processing states. Part of the AI pack.

AI pack starts at $249/yr Y1 (renews at $174/yr). Or get every Pro pack with Solo for $999/yr.

Styling

Three layers of customization, in order of escape-hatch strength: className overrides → data-attribute targeting → CSS custom properties.

Passing Tailwind classes

Every Oshon component accepts a className prop merged AFTER the component's default classes. Use it to override spacing, color, or size without forking the component.

tsx
<AIChat
  className="ring-2 ring-offset-2 ring-blue-500"
/>

Data attributes

Oshon components expose their internal state as data-oshon-* attributes so you can target them from CSS without coupling to internal class names. The most common attributes are listed below — see the component's source for the full set.

AttributeValuesDescription
data-oshon-sizexs · s · m · l · mobileVisual size axis. Mirrors the `size` prop.
data-oshon-tierprimary · secondary · tertiaryVisual emphasis tier (Button family). Mirrors the `tier` prop.
data-oshon-stateenabled · active · error · disabledComponent surface state. Set automatically based on props.
data-disabledtrue · (omitted)Set when `disabled` is true. Pair with `:disabled` CSS for native input components.
data-stateopen · closed · checked · unchecked · …Radix-derived state for overlay components (Dialog, Tabs, Toggle, etc.).
css
/* Target the secondary tier specifically */
[data-oshon-tier="secondary"] {
  --oshon-color-primary-700: var(--my-brand-color);
}

Interactive states

Every interactive component supports the standard CSS pseudo- classes plus Tailwind's state variants. Focus rings always use :focus-visible so keyboard users see them but mouse users don't.

  • :hover / hover:* — pointer hover
  • :focus-visible / focus-visible:* — keyboard focus
  • :active / active:* — pressed
  • :disabled / disabled:* — set via the disabled prop

Anatomy

The named regions a consumer composes when rendering this component. Each is documented separately so you can target keyboard nav, ARIA labels, and slot props with precision.

title

Header heading (ReactNode). Renders Lato SemiBold 16/20 neutral-900 on the Desktop size. Truncates at container width.

titleIcon

Optional leading glyph that replaces the default 20×20 primary-700 sparkles icon.

welcomeMessage

First assistant bubble, rendered above any `messages`. Omit to hide the welcome row entirely.

suggestions

Right-aligned warning-100 chips below the welcome bubble. Each `{ id, label }` is a clickable suggestion; clicking fires `onSelectSuggestion(entry)`. Hidden once `messages` is non-empty unless `alwaysShowSuggestions` is set.

messages

Transcript. Each `{ id, role, content, timestamp?, streaming? }` renders a bubble. `role: "assistant"` → left-aligned 272px bubble on surface + neutral-300 border with square bottom-left. `role: "user"` → right-aligned bubble on neutral-200 with square bottom-right. `streaming: true` paints the primary-500 streaming dot next to the last character.

notice

Optional neutral-100 footer band (legal/disclaimer). Renders Lato Regular 10/12 neutral-600 per Figma.

Keyboard

Tab: move through header close → transcript suggestions → composer input → send/stop. Enter in composer: submit (no-op while empty or processing). Shift+Enter: default browser behavior (no submit). Esc: close (parent popover/dialog owns visibility).

Accessibility

Every Oshon component ships axe-clean. We test in CI on every PR and publish the audit log per component.

WCAG level
2.2 AA
Screen readers tested
VoiceOver (macOS)
Last axe audit
2026-04-21

Do / Don't

✓ Do

Initial state (welcome + suggestions)
<AIChat title="FSMA 204 Answers" welcomeMessage="How can I help?" suggestions={[{ id: "q1", label: "Which melons are FTL?" }]} onSend={ask} />
Typing state (focused composer with draft)
<AIChat title="FSMA 204 Answers" inputValue={draft} onInputChange={setDraft} onSend={ask} />
Processing state (Stop button)
<AIChat title="FSMA 204 Answers" messages={msgs} processing onStop={stop} />
Response with streaming bubble
<AIChat title="FSMA 204 Answers" messages={[{ id: "a1", role: "assistant", content: text, streaming: true }]} />

✗ Don't

Rendering AIChat children directly
<AIChat><AIChat.Message /></AIChat>

AIChat is data-driven by design (Oshon principle #2). Pass a `messages` array. For custom bubble content, use the `content` field — it accepts any ReactNode.

Controlling visibility from inside AIChat
<AIChat open={open} onOpenChange={setOpen} />

AIChat is the surface. Top-level visibility belongs to a parent Popover/Dialog. AIChat exposes `onClose` which you wire to the parent.

Inlining brand text
<AIChat title="Oshon AI" notice="Oshon may make mistakes" />

Principle #11 — every user-visible string comes from a message catalog. Pass translated nodes via props; do not hard-code brand strings inside the component.

Design rationale

Phase 4f visual fidelity import. Pixel port of Figma OSH AI Chat UI (page 8196:54806 — compact popup row "AI Chat 2" at y≈2143). Container is 348×572 on the Desktop size with a drop shadow keyed to the primary-700 chroma rgba(5,91,93,0.2) per Figma. Header uses the 52px Lato SemiBold 16/20 title convention shared with Oshon Dialog. Assistant bubbles invert the user bubble corner geometry (square bottom-left vs. square bottom-right) to signal direction at a glance. Suggestion chips sit on secondary-200 (tradewinds-200) with mix-blend-multiply so a toolbar theme swap still lets the warning chroma tint through. Composer border flips to primary-700 on focus-within, matching the Figma Typing state exactly. `processing` swaps Send→Stop in place so the keyboard focus ring never jumps mid-response. Transcript scroll is managed internally on every `messages.length` / `processing` change so callers don’t have to wire a ref. Five-size surface preserves immutable rule #2; only Desktop is Figma-locked.