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.