Skip to Content

Theming

You can theme reachat by passing a custom theme to the Chat component.

import { Chat, chatTheme } from 'reachat'; export default function App() { return <Chat theme={chatTheme} {...rest} />; }

reachat exports three things you’ll typically reach for when theming:

  • chatTheme β€” the full default theme (Tailwind class strings, with light + dark: variants)
  • ChatTheme β€” the full theme interface
  • PartialChatTheme β€” a deep-Partial<ChatTheme> for incremental overrides

For most apps you’ll want to spread chatTheme and override just the bits you need:

import { chatTheme, type PartialChatTheme } from 'reachat'; import { twMerge } from 'tailwind-merge'; const theme: PartialChatTheme = { ...chatTheme, messages: { ...chatTheme.messages, message: { ...chatTheme.messages.message, question: twMerge(chatTheme.messages.message.question, 'text-purple-300') } } };

Theme Interface

reachat ships a comprehensive theming system covering every visible piece of the chat β€” sessions list, message bubbles, the markdown pipeline, the rich-text input (mentions/commands popup, tag chips, editor), the MessageStatus indicator, ChatSuggestions, charts, and any LLM-driven Component Catalog output.

The theme interface is as follows:

export interface ChatTheme { base: string; console: string; companion: string; empty: string; appbar: string; // MessageStatus component theming status: { base: string; header: string; icon: { base: string; loading: string; complete: string; error: string; }; text: { base: string; loading: string; complete: string; error: string; }; steps: { base: string; step: { base: string; icon: string; text: string; loading: string; complete: string; error: string; }; }; }; sessions: { base: string; console: string; companion: string; create: string; group: string; session: { base: string; active: string; delete: string; }; }; messages: { base: string; console: string; companion: string; back: string; inner: string; title: string; date: string; content: string; header: string; showMore: string; message: { base: string; question: string; response: string; cursor: string; overlay: string; expand: string; scrollToBottom: { container: string; button: string; }; files: { base: string; file: { base: string; name: string; }; }; sources: { base: string; source: { base: string; companion: string; image: string; title: string; url: string; }; }; markdown: { hr: string; p: string; a: string; table: string; th: string; td: string; code: string; inlineCode: string; toolbar: string; li: string; ul: string; ol: string; copy: string; h1: string; h2: string; h3: string; h4: string; h5: string; h6: string; }; footer: { base: string; copy: string; upvote: string; downvote: string; refresh: string; }; }; }; // ChatInput, mentions/commands popup, tag chips, editor input: { base: string; upload: string; input: string; actions: { base: string; send: string; stop: string; }; popup: { base: string; content: string; item: string; itemHighlighted: string; itemIcon: string; itemContent: string; itemLabel: string; itemDescription: string; itemShortcut: string; empty: string; loading: string; }; tag: { base: string; mention: string; command: string; }; editor: { base: string; container: string; placeholder: string; }; }; // ChatSuggestions component theming suggestions: { base: string; item: { base: string; icon: string; text: string; }; }; // Chart rendering theming (used by createChartComponentDef) chart: { base: string; title: string; content: string; error: { base: string; title: string; code: string; }; warning: { base: string; title: string; }; }; // Wrapper around Component Catalog-rendered components component: { base: string; }; }

Theme keys cheat sheet

Top-level keyCovers
base, console, companion, emptyLayout containers per view type
appbar<AppBar> header slot
sessionsSessions list, session items, group headings, new-session button
messagesMessage panel header, content, back button, show-more
messages.messageQuestion/response bubbles, streaming cursor, expand overlay
messages.message.filesFile attachment chips on questions
messages.message.sourcesCitation/source cards
messages.message.markdownAll markdown elements rendered in a response
messages.message.footerCopy / upvote / downvote / refresh action buttons
messages.message.scrollToBottomFloating scroll-to-bottom button
inputOuter input shell (base, upload, input, actions)
input.popupMention/command suggestion popup
input.tagInline mention and command chips inside the editor
input.editorTiptap editor styling and placeholder
suggestions<ChatSuggestions> chip list
status<MessageStatus> icon, text, multi-step list
chartChart container, error, and warning states
componentWrapper around Component Catalog output

Example Theme

Below is a complete example theme covering every section of the interface:

export const chatTheme: ChatTheme = { base: 'dark:text-white text-gray-500', console: 'flex w-full gap-4 h-full', companion: 'w-full h-full overflow-hidden', empty: 'text-center flex-1', appbar: 'flex items-center justify-between px-4 py-2 border-b border-gray-200 dark:border-gray-800', // MessageStatus theming status: { base: 'flex items-start gap-2 p-3 rounded-lg bg-gray-50 dark:bg-gray-900/50', header: 'flex items-center gap-2', icon: { base: 'w-4 h-4', loading: 'text-blue-500 animate-spin', complete: 'text-green-500', error: 'text-red-500' }, text: { base: 'text-sm font-medium', loading: 'text-gray-600 dark:text-gray-400', complete: 'text-green-600 dark:text-green-400', error: 'text-red-600 dark:text-red-400' }, steps: { base: 'ml-6 mt-2 space-y-1', step: { base: 'flex items-center gap-2 text-sm', icon: 'w-3 h-3', text: 'text-gray-600 dark:text-gray-400', loading: 'text-blue-500', complete: 'text-green-500', error: 'text-red-500' } } }, sessions: { base: 'overflow-auto', console: 'min-w-[150px] w-[30%] max-w-[300px] dark:bg-[#11111F] bg-[#F2F3F7] p-5 rounded-3xl', companion: 'w-full h-full', group: 'text-xs dart:text-gray-400 text-gray-700 mt-4 hover:bg-transparent mb-1', create: 'relative mb-4 rounded-[10px] text-white', session: { base: [ 'group my-1 rounded-[10px] p-2 text-gray-500 border border-transparent hover:bg-gray-300 hover:border-gray-400 [&_svg]:text-gray-500', 'dark:text-typography dark:text-gray-400 dark:hover:bg-gray-800/50 dark:hover:border-gray-700/50 dark:[&_svg]:text-gray-200' ].join(' '), active: [ 'border border-gray-300 hover:border-gray-400 text-gray-700 bg-gray-200 hover:bg-gray-300 ', 'dark:text-gray-500 dark:bg-gray-800/70 dark:border-gray-700/50 dark:text-white dark:border-gray-700/70 dark:hover:bg-gray-800/50', '[&_button]:!opacity-100' ].join(' '), delete: '[&>svg]:w-4 [&>svg]:h-4 opacity-0 group-hover:!opacity-50' } }, messages: { base: '', console: 'flex flex-col mx-5 flex-1 overflow-hidden', companion: 'flex w-full h-full', back: 'self-start p-0 my-2', inner: 'flex-1 h-full flex flex-col', title: ['text-base font-bold text-gray-500', 'dark:text-gray-200'].join( ' ' ), date: 'text-xs whitespace-nowrap text-gray-400', content: [ 'mt-2 flex-1 overflow-auto [&_hr]:bg-gray-200', 'dark:[&_hr]:bg-gray-800/60' ].join(' '), header: 'flex justify-between items-center gap-2', showMore: 'mb-4', message: { base: 'mt-4 mb-4 flex flex-col p-0 rounded border-none bg-transparent', question: [ 'relative font-semibold mb-4 px-4 py-4 pb-2 rounded-3xl rounded-br-none text-typography border bg-gray-200 border-gray-300 text-gray-900', 'dark:bg-gray-900/60 dark:border-gray-700/50 dark:text-gray-100' ].join(' '), response: ['relative data-[compact=false]:px-4 text-gray-900', 'dark:text-gray-100'].join(' '), overlay: `overflow-y-hidden max-h-[350px] after:content-[''] after:absolute after:inset-x-0 after:bottom-0 after:h-16 after:bg-gradient-to-b after:from-transparent dark:after:to-gray-900 after:to-gray-200`, cursor: 'inline-block w-1 h-4 bg-current', expand: 'absolute bottom-1 right-1 z-10', scrollToBottom: { container: 'absolute bottom-20 right-4 z-10', button: 'p-2 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-gray-800 dark:hover:bg-gray-700 shadow-lg' }, files: { base: 'mb-2 flex flex-wrap gap-3 ', file: { base: [ 'flex items-center gap-2 border border-gray-300 px-3 py-2 rounded-lg cursor-pointer', 'dark:border-gray-700' ].join(' '), name: ['text-sm text-gray-500', 'dark:text-gray-200'].join(' ') } }, sources: { base: 'my-4 flex flex-wrap gap-3', source: { base: [ 'flex gap-2 border border-gray-200 px-4 py-2 rounded-lg cursor-pointer', 'dark:border-gray-700' ].join(' '), companion: 'flex-1 px-3 py-1.5', image: 'max-w-10 max-h-10 rounded-md w-full h-fit self-center', title: 'text-md block', url: 'text-sm text-blue-400 underline' } }, markdown: { copy: 'sticky py-1 [&>svg]:w-4 [&>svg]:h-4 opacity-50', p: 'mb-2', a: 'text-blue-400 underline', table: 'table-auto w-full m-2', th: 'px-4 py-2 text-left font-bold border-b border-gray-500', td: 'px-4 py-2', code: 'm-2 rounded-b relative', toolbar: 'text-xs dark:bg-gray-700/50 flex items-center justify-between px-2 py-1 rounded-t sticky top-0 backdrop-blur-md bg-gray-200 ', li: 'mb-2 ml-6', ul: 'mb-4 list-disc', ol: 'mb-4 list-decimal' }, footer: { base: 'mt-3 flex gap-1.5 text-gray-400', copy: [ 'p-3 rounded-[10px] [&>svg]:w-4 [&>svg]:h-4 opacity-50 hover:!opacity-100 hover:bg-gray-200 hover:text-gray-500', 'dark:hover:bg-gray-800 dark:hover:text-white' ].join(' '), upvote: 'p-3 rounded-[10px] [&>svg]:w-4 [&>svg]:h-4 opacity-50 hover:!opacity-100 hover:bg-gray-700/40 hover:text-white', downvote: 'p-3 rounded-[10px] [&>svg]:w-4 [&>svg]:h-4 opacity-50 hover:!opacity-100 hover:bg-gray-700/40 hover:text-white', refresh: 'p-3 rounded-[10px] [&>svg]:w-4 [&>svg]:h-4 opacity-50 hover:!opacity-100 hover:bg-gray-700/40 hover:text-white' } } }, input: { base: 'flex mt-4 relative', upload: ['px-5 py-2 text-gray-400 size-10', 'dark:gray-500'].join(' '), input: [ 'w-full border rounded-3xl px-3 py-2 pr-16 text-gray-500 border-gray-200 hover:bg-blue-100 hover:border-blue-500 after:hidden after:!mx-10 bg-white [&>textarea]:w-full [&>textarea]:flex-none', 'dark:border-gray-700/50 dark:text-gray-200 dark:bg-gray-950 dark:hover:bg-blue-950/40' ].join(' '), actions: { base: 'absolute flex gap-2 items-center right-5 inset-y-1/2 -translate-y-1/2 z-10', send: [ 'px-3 py-3 hover:bg-primary-hover rounded-full bg-gray-200 hover:bg-gray-300 text-gray-500', 'dark:text-white dark:bg-gray-800 hover:dark:bg-gray-700' ].join(' '), stop: 'px-2 py-2 bg-red-500 text-white rounded-full hover:bg-red-700 ' }, // Mentions and commands popup theming popup: { base: 'absolute bottom-full mb-2 left-0 w-80 max-h-64 overflow-y-auto rounded-lg shadow-lg border border-gray-200 dark:border-gray-700', content: 'bg-white dark:bg-gray-900', item: 'flex items-center gap-3 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer', itemHighlighted: 'bg-gray-100 dark:bg-gray-800', itemIcon: 'w-5 h-5 text-gray-500 dark:text-gray-400', itemContent: 'flex-1 min-w-0', itemLabel: 'text-sm font-medium text-gray-900 dark:text-gray-100', itemDescription: 'text-xs text-gray-500 dark:text-gray-400 truncate', itemShortcut: 'text-xs text-gray-400 dark:text-gray-500', empty: 'px-3 py-6 text-center text-sm text-gray-500 dark:text-gray-400', loading: 'px-3 py-6 text-center text-sm text-gray-500 dark:text-gray-400' }, tag: { base: 'inline-flex items-center gap-1 px-2 py-0.5 rounded text-sm font-medium', mention: 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300', command: 'bg-purple-100 text-purple-800 dark:bg-purple-900/50 dark:text-purple-300' }, editor: { base: 'flex-1', container: 'w-full', placeholder: 'text-gray-400 dark:text-gray-500' } }, // ChatSuggestions theming suggestions: { base: 'flex flex-wrap gap-2', item: { base: 'flex items-center gap-2 px-4 py-2 rounded-full bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700 cursor-pointer transition-colors', icon: 'w-4 h-4 text-gray-500 dark:text-gray-400', text: 'text-sm text-gray-700 dark:text-gray-300' } }, // Chart theming chart: { base: 'my-4 p-4 rounded-lg bg-gray-50 dark:bg-gray-900/50', title: 'text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2', content: 'w-full', error: { base: 'p-4 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800', title: 'text-sm font-semibold text-red-700 dark:text-red-400 mb-2', code: 'text-xs text-red-600 dark:text-red-500 font-mono' }, warning: { base: 'p-4 rounded-lg bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800', title: 'text-sm font-semibold text-yellow-700 dark:text-yellow-400' } } };