Skip to Content

Custom Components

reachat is completely customizable. You can change the look and feel of the chat by tweaking the theme or by replacing entire components via the slot pattern (similar to Radix’s Slot). Pass a child to any composable component (e.g. <MessageQuestion>, <SessionListItem>, <NewSessionButton>) and reachat will render your child in place while still passing through the props it would have rendered itself.

Custom Components Example

Below is example markup that creates a custom session empty message and also provides a custom session list item.

const CustomMessageQuestion: FC<any> = ({ question, files }) => ( <> <span className="text-lg font-semibold text-blue-500"> This is my question: {question} </span> <MessageFiles files={files}> <CustomMessageFile /> </MessageFiles> </> ); const CustomMessageResponse: FC<any> = ({ response }) => ( <blockquote className="border-l border-blue-500 pl-2"> This is the response: {response} </blockquote> ); const CustomMessageFile: FC<any> = ({ name, type }) => ( <Chip size="small" className="rounded-full border border-gray-700"> {name || type} </Chip> ); const CustomMessageSource: FC<any> = ({ title, url, image }) => { const { theme } = useContext(ChatContext); return ( <Chip size="small" className="rounded-full border border-blue-500 border-opacity-50" onClick={() => alert('take me to ' + url)} start={ image && ( <img src={image} alt={title} className={cn(theme.messages.message.sources.source.image)} /> ) } > {title || url} </Chip> ); }; const CustomSessionListItem: FC<SessionListItemProps> = ({ session, children, ...rest }) => { const [open, setOpen] = useState(false); const btnRef = useRef(null); return ( <> <ListItem {...rest} end={ <IconButton ref={btnRef} size="small" variant="text" onClick={e => { e.stopPropagation(); setOpen(true); }} > <MenuIcon /> </IconButton> } > <span className="truncate">{session.title}</span> </ListItem> <Menu open={open} onClose={() => setOpen(false)} reference={btnRef} appendToBody={false} > <Card disablePadding> <List> <ListItem onClick={() => alert('rename')}>Rename</ListItem> <ListItem onClick={() => alert('delete')}>Delete</ListItem> </List> </Card> </Menu> </> ); };

Then in the master component you can leverage them like:

export const CustomComponents = () => { return ( <div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, padding: 20, margin: 20, background: '#02020F', borderRadius: 5 }} > <Chat sessions={[ ...fakeSessions, ...sessionsWithFiles, ...sessionWithSources ]} activeSessionId="1" > <SessionsList> <NewSessionButton> <button className="text-blue-500">New Session</button> </NewSessionButton> <Divider variant="secondary" /> <SessionGroups> {groups => groups.map(({ heading, sessions }) => ( <SessionsGroup heading={heading} key={heading}> {sessions.map(s => ( <SessionListItem key={s.id} session={s}> <CustomSessionListItem session={s} /> </SessionListItem> ))} </SessionsGroup> )) } </SessionGroups> </SessionsList> <SessionMessagePanel> <SessionMessagesHeader> <CustomMessagesHeader /> </SessionMessagesHeader> <SessionMessages> {conversations => conversations.map((conversation, index) => ( <SessionMessage conversation={conversation} isLast={index === conversations.length - 1} key={conversation.id} > <MessageQuestion question={conversation.question} files={conversation.files} > <CustomMessageQuestion /> </MessageQuestion> <MessageResponse response={conversation.response}> <CustomMessageResponse /> </MessageResponse> <MessageSources sources={conversation.sources}> <CustomMessageSource /> </MessageSources> <MessageActions question={conversation.question} response={conversation.response} /> </SessionMessage> )) } </SessionMessages> <ChatInput /> </SessionMessagePanel> </Chat> </div> ); };

Using ChatContext

reachat exposes a ChatContext that custom components can subscribe to via React’s useContext hook. This is how you reach into the active chat from deep inside a slot — to dispatch a message, read the active session, check loading state, etc.

import { useContext } from 'react'; import { ChatContext } from 'reachat'; const CustomSendButton = () => { const { sendMessage } = useContext(ChatContext); return ( <button onClick={() => sendMessage?.('Hello, AI!')}> Send Custom Message </button> ); };

ChatContext provides the following properties and methods:

export interface ChatContextProps { sessions: Session[]; activeSessionId: string | null; activeSession?: Session | null; disabled?: boolean; isLoading?: boolean; isCompact?: boolean; viewType?: 'chat' | 'companion' | 'console'; theme?: ChatTheme; remarkPlugins?: Plugin[]; markdownComponents?: Components; selectSession?: (sessionId: string) => void; deleteSession?: (sessionId: string) => void; createSession?: () => void; sendMessage?: (message: string) => void; stopMessage?: () => void; fileUpload?: (file: File | File[]) => void; }

By combining slot overrides with ChatContext, you can build a fully customized chat experience while still leveraging the rest of reachat’s behavior.