Chat dock

ChatDock is the AI tutor as one element that changes shape: the Ask Nuri pill, the compact panel, and on the web a maximized panel. Nuri's head glides from the pill into the header as it opens, and the conversation never resets.

  • Beta
  • Accessibility
  • Figma Ask Nuri 4707:183711
  • packages/ui/src/components/ChatDock

Beta until the motion (code-only) is confirmed by design. The pill follows Figma 'Ask Nuri' 4707:183711.

Preview

Open it, maximize it, press Escape, close it. The frame stands in for the app screen.

Iterative design process

Good design is rarely right the first time. Teams work in loops: make something small, test it with people, learn what went wrong and improve it.

Nuri

Your learning companion

What do you need clarity on?

Iterative design process

Good design is rarely right the first time. Teams work in loops: make something small, test it with people, learn what went wrong and improve it.

Nuri

Your learning companion

What do you need clarity on?

Installation

Availability

@nuton/ui and @nuton/tokens are private workspace packages and aren't published yet (D4, D12). Inside the Nuton monorepo, add them as workspace dependencies.

bash
pnpm add @nuton/ui @nuton/tokens @remixicon/react
tsx
import '@nuton/tokens/tokens.css'
import '@nuton/ui/styles.css'

Usage

Put the dock inside a positioned container that fills the screen (for example the page's root). It anchors to that container: its bottom right as a side panel, or its bottom centre as a sheet. Keep the conversation, and the draft in your composer, in your own state: the dock stays mounted, so nothing resets when it opens, closes or resizes.

  • Web: layout="side" (default) with onMaximizedChange to show the maximize and restore button. Make room beside it in your layout while the text keeps a readable measure; below about 1280px let it float over the content.
  • Tablet and phone: layout="sheet". The pill sits at the bottom centre and rises into a sheet (a centred card on a tablet, the full width on a phone). While open it is a dialog and lifts itself above the on-screen keyboard. There is no maximize button.
  • insetTop and insetBottom keep it clear of a top bar and of something docked at the bottom, such as the podcast player.

Variants and sizes

Three shapes of one element.

Pill: 121 × 44 (hover widens it to 141 with an arrow). Compact panel: 28% of the container, 360 to 420px wide. Maximized panel: 46% of the container, 480 to 760px. All of them move width, height and corner radius together over 460ms.

States and interactions

Opening: the pill grows into the panel, Nuri's head glides to the header, the content fades in once there is room. Maximizing and restoring: the same panel resizes. Closing: the reverse; the pill returns where it started. Escape restores a maximized panel and then closes it. Focus goes to the question box when it opens and back to the pill when it closes. Reduced motion shortens everything to a quick fade.

Composition examples

In Learner → Lesson the dock is the AI tutor, with the lesson making room beside it and the podcast player docked under it. It uses Chat pieces for its content and Chat launcher's pill design for its closed state.

API reference

Props

Chat dock props
PropTypeDefaultDescription
open / onOpenChangeboolean / (open) => void—The panel (true) or the pill (false). Required.
maximized / onMaximizedChangeboolean / (max) => void—The maximized panel. Giving onMaximizedChange shows the maximize and restore button (side layout only).
layout'side' | 'sheet''side'Bottom right as a side panel, or bottom centre as a sheet.
title, subtitleReactNode'Nuri', 'Your learning companion'The header text.
avatarReactNode—The header picture. Default is Nuri's head, alive.
children, footerReactNode—The conversation, and the composer under it.
actionsReactNode—Extra header buttons, before maximize and close.
panelRefRef<HTMLElement>—The inner ChatPanel element.
busyboolean—Shows the thinking state in the panel.
insetTop, insetBottomstring—Space to keep clear at the top and bottom of the container (any CSS length).
initialFocus() => HTMLElement | null—Where focus goes when the panel opens. Default: the first text field.
labelsPartial<ChatDockLabels>—Override the English strings (launcher words, panel name, buttons, announcements).

Accessibility

Keyboard

KeyBehaviour
TabMoves through the pill, or the panel when open.
Enter / SpacePresses the focused button.
EscRestores a maximized panel, then closes the panel.

Screen readers

The panel is a named region; in the sheet layout it is a dialog while open. A polite status announces opened, closed, maximized and restored. The closed panel is inert, so it cannot be tabbed into.

Focus

Opening moves focus into the panel. Closing returns it to the pill, and the draft is kept.

Contrast and colour

Every surface and label uses the design system's tokens, in light and dark. Open, closed and maximized are shown by shape and position, not colour.

Touch and pointer

The pill and the panel buttons are at least 44px. The motion is removed with reduced motion.

Figma mapping

The pill is "Ask Nuri" 4707:183711. The panel is the Chat panel. The motion and sizes are code-only (C125, C126).

Guidelines and related components