Elements is a library built for the agentic age. We provide customizable and elegant UI primitives for building chat-like experiences for MCP Servers. Works best with Gram MCP, but also supports connecting any MCP Server.
Setup
Frontend Setup
The easiest way to install Elements and all its peer dependencies:
npx @gram-ai/elements install
This will automatically detect your package manager and install everything you need.
Manual installation
If you prefer to install manually, first install the peer dependencies:
If you’re only using the server handlers (@gram-ai/elements/server), you can install without needing to install any of the React peer dependencies:
pnpm add @gram-ai/elements
Your package manager may show peer dependency warnings for React packages. These warnings are safe to ignore when using only /server exports, as React dependencies are marked as optional.
Setting up your backend
We provide framework-specific adapters to make it easy to set up a backend endpoint for Gram Elements. These adapters handle the session token creation securely on your server without exposing your GRAM_API_KEY to the browser.
Express
import { createExpressHandler } from '@gram-ai/elements/server/express'import express from 'express'const app = express()app.use(express.json())app.post('/chat/session', createExpressHandler({ embedOrigin: 'http://localhost:3000', userIdentifier: 'user-123', expiresAfter: 3600, // optional, max 3600 seconds}))app.listen(3000)
import { createHonoHandler } from '@gram-ai/elements/server/hono'import { Hono } from 'hono'const app = new Hono()app.post('/chat/session', createHonoHandler({ embedOrigin: 'http://localhost:3000', userIdentifier: 'user-123', expiresAfter: 3600,}))export default app
Environment Variables
All adapters require the GRAM_API_KEY environment variable to be set:
GRAM_API_KEY=xxx
This key is kept on your server and never exposed to the browser, ensuring secure communication with Gram’s API.
Setting up your frontend
@gram-ai/elements requires that you wrap your React tree with our context provider:
import { GramElementsProvider, Chat, type ElementsConfig } from '@gram-ai/elements'// Please fill out projectSlug and mcpconst config: ElementsConfig = { projectSlug: 'xxx', mcp: 'https://app.getgram.ai/mcp/{your_slug}', variant: 'widget', welcome: { title: 'Hello!', subtitle: 'How can I help you today?', }, composer: { placeholder: 'Ask me anything...', }, modal: { defaultOpen: true, },}export const App = () => { return ( <GramElementsProvider config={config}> <Chat /> </GramElementsProvider> )}
Custom Session Endpoint
By default, Elements expects the session endpoint on your backend to be located at /chat/session. If you’ve mounted your session endpoint at a different path, you can provide a custom getSession function in the ElementsProvider:
For complete configuration options and TypeScript type definitions, see the API documentation.
Quick Configuration Example
import { GramElementsProvider, Chat, type ElementsConfig } from '@gram-ai/elements'const config: ElementsConfig = { projectSlug: 'your-project', mcp: 'https://app.getgram.ai/mcp/your-mcp-slug', variant: 'widget', // 'widget' | 'sidecar' | 'standalone' welcome: { title: 'Hello!', subtitle: 'How can I help you today?', },}export const App = () => { return ( <GramElementsProvider config={config}> <Chat /> </GramElementsProvider> )}
Plugins
Plugins extend the Gram Elements library with custom rendering capabilities for specific content types. They allow you to transform markdown code blocks into rich, interactive visualizations and components.
How Plugins Work
When you add a plugin:
The plugin extends the system prompt with instructions for the LLM
The LLM returns code blocks marked with the plugin’s language identifier
The plugin’s custom component renders the code block content
For example, the built-in chart plugin instructs the LLM to return Vega specifications for visualizations, which are then rendered as interactive charts.
Using Recommended Plugins
Gram Elements includes a set of recommended plugins that you can use out of the box:
import { GramElementsProvider, Chat, type ElementsConfig } from '@gram-ai/elements'import { recommended } from '@gram-ai/elements/plugins'const config: ElementsConfig = { projectSlug: 'my-project', mcp: 'https://app.getgram.ai/mcp/my-mcp-slug', welcome: { title: 'Hello!', subtitle: 'How can I help you today?', }, // Add all recommended plugins plugins: recommended,}export const App = () => { return ( <GramElementsProvider config={config}> <Chat /> </GramElementsProvider> )}
Available Recommended Plugins
chart - Renders Vega chart specifications as interactive visualizations
Using Individual Plugins
You can also import and use plugins individually:
import { chart } from '@gram-ai/elements/plugins'const config: ElementsConfig = { // ... other config plugins: [chart],}
Using Custom Plugins
You can create your own custom plugins to add specialized rendering capabilities:
import { GramElementsProvider, Chat, type ElementsConfig } from '@gram-ai/elements'import { chart } from '@gram-ai/elements/plugins'import { myCustomPlugin } from './plugins/myCustomPlugin'const config: ElementsConfig = { projectSlug: 'my-project', mcp: 'https://app.getgram.ai/mcp/my-mcp-slug', welcome: { title: 'Hello!', subtitle: 'How can I help you today?', }, // Combine built-in and custom plugins plugins: [chart, myCustomPlugin],}export const App = () => { return ( <GramElementsProvider config={config}> <Chat /> </GramElementsProvider> )}
Replay lets you play back a pre-recorded conversation with streaming animations — no auth, MCP, or network calls required. This is useful for marketing demos, documentation, and testing.
Recording a cassette
A cassette is a JSON file that captures a conversation. To record one from a live chat session, enable the built-in recorder by setting this environment variable:
VITE_ELEMENTS_ENABLE_CASSETTE_RECORDING=true
This adds a record button to the composer. Click it to open the recorder popover, then start recording. Chat normally, and when you’re done, click Stop & Download to save the cassette as a .cassette.json file.
You can also record programmatically using the useRecordCassette hook:
React 18 and 19 work out of the box. For React 16 or 17, you need to configure your bundler to shim newer React APIs (useSyncExternalStore, useId, useInsertionEffect) that are used by transitive dependencies like zustand and @assistant-ui/react.
React 16 and React 17 are not regularly tested. If you run into any issues using Elements with these versions, please reach out to us for support.
Vite Setup (React 16/17)
Add the compatibility plugin to your Vite config:
import react from '@vitejs/plugin-react'import { reactCompat } from '@gram-ai/elements/compat'import { defineConfig } from 'vite'export default defineConfig({ plugins: [react(), reactCompat()],})
This automatically shims React 18 APIs (useSyncExternalStore, useId, useInsertionEffect) so that Elements and its dependencies work correctly on older React versions.
Contributing
We welcome pull requests to Elements. Please read the contributing guide.