Hashbrown: letting a model render your own React or Angular components
Hashbrown is a framework for building agents that run the browser. Built for Angular and React.
At a glance
- What is it?
- Hashbrown is a headless TypeScript framework where the model composes components you have already registered, tools run inside your app rather than on a server, and the schema language called Skillet validates the props. It ships no chat interface of its own, which is the design decision everything else follows from.
- Who is it for?
- Adopt Hashbrown if you are adding an assistant to an existing React or Angular application and want the model to drive your own components and your own client-side state rather than a generic chat widget, since the whole framework is built around exposed components and Skillet-validated props. Keep away if you want a drop-in chat interface or a server-side tool runtime, because the design deliberately runs tools in the browser and ships no UI.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Headless, and the model renders only what you register
Hashbrown is a headless TypeScript framework for AI chat and agents in React and Angular. The framing in the README is that the model renders your components and calls your tools, in the browser, with any provider.
The most important sentence about the framework is that it ships no chat UI of its own, you keep your components and your design system. That single decision shapes the rest of the design, and it is the difference between this and a chat widget library.
The four headline features follow from it. Generative UI means the model only renders components you register, and Skillet schemas validate their props. Tools in the browser means tools run in your app, with its state and services. Built for streaming means components render while the response streams in. And any model means your server keeps the API key and streams from OpenAI, Anthropic, Gemini, Bedrock, Azure, or Ollama.
The consequence for an adopter is worth stating plainly. If your team already has a design system, Hashbrown gives you a way for a model to use it. If your team has no design system, you are starting from nothing, because there is no default interface to fall back on.
The project is published under the @hashbrownai scope on npm, with a documentation site at hashbrown.dev, and the version line is at 0.7.0 published on 2026-10-08, after 0.5.0 on 2026-07-13. The default branch is main, the last push was on 2026-10-20, and the licence is a custom one that GitHub cannot classify, with the licence file in the repository.
Three packages, and the install line for each framework
The package layout is simple and the naming makes the layering clear.
@hashbrownai/core holds the framework-agnostic primitives, including the Skillet schema language and the streaming JSON parser. @hashbrownai/<angular|react> holds the resources, hooks and components for your framework. And @hashbrownai/<provider> is a Node adapter that streams from a model provider's SDK.
So the core has no framework dependency, the framework package has no provider dependency, and the provider adapter is the only thing that knows which vendor you are talking to. That is the arrangement that lets the same application code work on React and Angular, which the README claims as the first line of the how it works section.
The install for Angular with OpenAI is:
npm install @hashbrownai/{core,angular,openai}And for React with OpenAI:
npm install @hashbrownai/{core,react,openai}The brace expansion is worth noting, because it installs three packages with one command and gets the scopes right in a single step.
There are quick start pages for both frameworks on the documentation site, and the same documentation is maintained in both variants, with Angular and React intro pages linked side by side from the README. The features table also links every feature to both, which suggests the two framework implementations are held in step deliberately rather than one being a port of the other.
Exposing a component, and what Skillet adds to the props
The first step of the flow is registering your components. The model can only render what you have exposed, and each exposure carries a name, a description, and a set of props expressed in the Skillet schema language.
In Angular:
const components = [
exposeComponent(FlightCardComponent, {
name: 'FlightCard',
description: 'One aircraft on the map, with live altitude and speed.',
input: {
note: s.streaming.string('A sentence or two about this flight'),
hex: s.string('The aircraft hex code from a tool result.'),
},
children: false,
}),
];And in React:
const components = [
exposeComponent(FlightCard, {
name: 'FlightCard',
description: 'One aircraft on the map, with live altitude and speed.',
props: {
note: s.streaming.string('A sentence or two about this flight'),
hex: s.string('The aircraft hex code from a tool result.'),
},
children: false,
}),
];Three things in there are worth pausing on.
The description is not a comment. It is the only thing the model sees to decide when to use your component, so it is prompt text, and it has to be written for a model rather than for a colleague reading your code.
The s.streaming.string() versus s.string() distinction is the streaming design made visible. A streaming prop can be filled in progressively as tokens arrive, and a plain string has to arrive whole. A note that is written sentence by sentence can stream, and a hex code that comes from a tool result cannot.
And children: false closes the component to having nested component content, which is a deliberate constraint on the component tree the model is allowed to build.
The difference between the two examples is the property key name, input in Angular and props in React, which is a small reminder that the two APIs are parallel rather than identical.
Tools that run in the browser, against your own state
The second step is giving the model tools, and this is where Hashbrown differs most from a server-side agent framework. Tools run in the browser, with your app's state and services.
In Angular, the chat resource takes the tools directly:
chat = uiChatResource({
system: 'Help users understand the planes on the map.',
components,
tools: [createTool(this.atcTools.findAircraft)],
});And in React the same thing is done through hooks:
export function Assistant() {
const store = useAtcStore();
const atcTools = useMemo(() => createBrowserAtcTools(store), [store]);
const findAircraft = useTool({ ...atcTools.findAircraft, deps: [atcTools] });
const chat = useUiChat({
system: 'Help users understand the planes on the map.',
components,
tools: [findAircraft],
});
// Render the stream (step 3).
}The useMemo with the store as a dependency is the pattern to notice. The tool is created from your application state, and it is recreated when that state changes, with the deps array passed to useTool so the framework knows when the underlying function has changed. That is the mechanism by which a tool keeps working against live state rather than a stale closure.
The trade is real and the framework is clear-eyed about it. Tools that run client-side have direct access to your state, your services and your session, which is exactly what you want for a tool that queries your own data, and exactly what you would not want for a tool that spends money or mutates something irreversible. The provider adapter runs on your server, so the model and the API key never reach the client, but the tools do.
The features table also notes that you can connect MCP servers, so the same tool interface covers both local functions and remote tool servers.
Rendering the stream, and pointing the framework at your server
The third step is rendering, and it is where the streaming promise is kept. Components render while the response streams in.
In Angular the template iterates over the messages and renders each one with a custom element:
@for (message of chat.value(); track $index) {
<hb-render-message [message]="message" />
}And in React the assistant returns the messages, choosing the UI form for assistant turns and the plain content for everything else:
return chat.messages.map((message) =>
message.role === 'assistant' ? message.ui : message.content,
);That branch is the interesting line. An assistant turn carries a ui property, which is the composed components, and a non-assistant turn carries content, which is text. So the model can produce a structured UI for its own messages and plain text for the rest, and the host application decides what to do with each.
Before you use the hooks or resources, you point them at your server. In Angular that is a provider:
export const appConfig: ApplicationConfig = {
providers: [provideHashbrown({ baseUrl: '/api/run' })],
};And in React it is a provider component:
<HashbrownProvider url="/api/run">{children}</HashbrownProvider>The URL is your endpoint, and the default is a relative path, which means the streaming server sits on the same origin as your application and the browser never needs a cross-origin key or a CORS conversation for the model call.
The features table lists a few more capabilities. Structured output says Skillet schemas turn model output into typed JSON. Streaming says strings, arrays and objects parse as they arrive, and that Magic Text streams Markdown. Code execution runs model-written JavaScript in a sandbox. And Any model covers OpenAI, Anthropic, Gemini, Bedrock, Azure, Ollama, or a model in the browser.
Where Hashbrown is the wrong choice
The first wrong case is a team that wants a chat interface. Hashbrown ships none. The value is in exposing your components and wiring your tools, and if the deliverable is a working chat box, you are assembling the interface yourself on top of a framework that deliberately does not provide one.
The second is a workflow where tools must be trusted with privileged actions. Because the tools run in the browser, a tool that changes server state has to be reached over the network anyway, and the client-side half of the framework is then doing nothing for it. A server-side agent runtime, where the tool calls are made where the credentials live, is the better shape for that.
The third is a React and Angular codebase that already has its own conventions. The parallel API is a benefit if you maintain both, and a cost if you maintain one. The two examples differ in small ways, the property key is input in one and props in the other, and any divergence in the two implementations shows up as an extra thing to test.
The fourth is a project that is not yet ready for a 0.x framework with a moving API. The version line is 0.7.0, the changelog is in the repository, and the framework spans several packages including the core primitives, two framework integrations and one adapter per provider. A large surface at a pre-1.0 version is a maintenance commitment.
The alternative worth weighing is calling the provider's SDK directly from your own backend and streaming to your own components. You lose the Skillet validation, the streaming JSON parser and the provider adapters, and you gain full control over the protocol and no dependency on a framework's release schedule. For a simple chat feature inside an existing application, that is often the smaller decision.
Editorial conclusion
Adopt Hashbrown if you are adding an assistant to an existing React or Angular application and want the model to drive your own components and your own client-side state rather than a generic chat widget, since the whole framework is built around exposed components and Skillet-validated props. Keep away if you want a drop-in chat interface or a server-side tool runtime, because the design deliberately runs tools in the browser and ships no UI. Before you start, read the provider adapter list, since the API key stays on your server and the framework is not a model client itself, and note the release cadence with 0.7.0 published on 2026-10-08 after 0.5.0 in July.
Frequently asked questions
What is Hashbrown and what does it do?
It is a headless TypeScript framework for AI chat and agents in React and Angular. The model renders components you register, with Skillet schemas validating their props, tools run in your browser with your own state and services, and components render while the response streams in. It ships no chat UI of its own.
How do I install Hashbrown for React or Angular?
For Angular with OpenAI, npm install @hashbrownai/{core,angular,openai}, and for React with OpenAI, npm install @hashbrownai/{core,react,openai}. The core package holds the framework-agnostic primitives, the framework package holds resources, hooks and components, and the provider package is a Node adapter that streams from that provider's SDK.
How do I expose my own components to the model in Hashbrown?
You call exposeComponent with your component, a name, a description and its props written in the Skillet schema language. The model can only render components you have registered, and the description is what the model reads to decide when to use it, so it is prompt text rather than a code comment.
Do Hashbrown tools run on the server or in the browser?
In the browser, with your app's state and services. The model and the API key stay on your server through a provider adapter that streams AG-UI events, but the tool functions themselves execute client-side, which is what lets a tool query your live application state.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/liveloveapp-hashbrown)Was this useful?