The frontend is evolving from rendering what users click into rendering what users mean.
For years, frontend engineering followed a relatively predictable model:
User
↓
Click button
↓
Frontend event handler
↓
API request
↓
Response
↓
Update component
But AI-native applications are changing that model.
A user might now type:
“Show me the cheapest flights to Delhi next weekend and keep the morning ones.”
Instead of navigating through five screens, selecting filters, opening dropdowns, and pressing Search, the interface can understand the intent and construct the appropriate UI dynamically.
That shift is commonly described as Generative UI or agentic UI.
And it is becoming one of the most interesting intersections between frontend engineering and UX design in 2026.
Current frontend discussions on DEV increasingly combine AI-assisted development, generative UI, AI-generated frontend review, modern React architecture, performance, and modern CSS.
At the same time, broader frontend trends are moving toward agentic interfaces, server-first applications, component-driven systems, accessibility, and AI-assisted workflows.
The interesting question isn't:
“How do I put a chatbot on my website?”
The better question is:
“How should an interface behave when the user's intent is more important than the individual UI control?”
Let's build one.
What Generative UI Actually Means
A traditional interface has a predefined set of screens.
function Dashboard() {
return (
<>
<Sidebar />
<Header />
<Analytics />
<RecentOrders />
</>
);
}
The developer determines the structure beforehand.
Generative UI introduces another layer:
User intent
↓
AI / application logic
↓
Structured UI decision
↓
Existing UI components
↓
Rendered interface
The important distinction is this:
The AI should generally decide what the user needs, not invent arbitrary HTML.
For example:
{
"intent": "compare_products",
"products": [
"MacBook Air",
"Dell XPS"
],
"ui": "comparison_table"
}
The frontend can then map that result to a trusted component.
const uiMap = {
comparison_table: ProductComparison,
product_grid: ProductGrid,
checkout: CheckoutPanel,
confirmation: ConfirmationCard,
};
function GeneratedUI({ result }) {
const Component = uiMap[result.ui];
return Component ? <Component {...result} /> : null;
}
This is much safer than allowing an AI model to generate arbitrary markup.
The New Frontend Architecture
A useful architecture looks something like this:
┌──────────────────┐
│ User │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Intent Detection │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ AI / Agent Layer │
└────────┬─────────┘
│
Structured data
│
▼
┌──────────────────┐
│ UI Resolver │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Card Table Form
│ │ │
└──────────────┼──────────────┘
▼
┌──────────────────┐
│ Browser │
└──────────────────┘
Notice what is missing:
The AI isn't responsible for the entire frontend.
The application still owns the design system, accessibility, interactions, validation, and visual language.
The model provides intelligence.
The frontend provides constraints.
That separation is critical.
1. Start With a Component Registry
Instead of letting an AI generate arbitrary components, define the components that your application supports.
type UIComponent =
| "product-card"
| "product-grid"
| "comparison-table"
| "search-results"
| "confirmation"
| "checkout";
const components = {
"product-card": ProductCard,
"product-grid": ProductGrid,
"comparison-table": ComparisonTable,
"search-results": SearchResults,
"confirmation": Confirmation,
"checkout": Checkout,
};
Now the AI can select from a known vocabulary.
function renderComponent(type: UIComponent, props: unknown) {
const Component = components[type];
if (!Component) {
throw new Error(`Unsupported UI component: ${type}`);
}
return <Component {...props} />;
}
This creates an important architectural boundary:
AI
│
│ "I need a comparison table"
▼
UI Registry
│
│ "ComparisonTable is approved"
▼
React
│
▼
Browser
The AI has decision-making authority, not unlimited rendering authority.
2. Give the Model a UI Schema
A schema makes the AI output predictable.
For example:
import { z } from "zod";
const ProductSchema = z.object({
id: z.string(),
name: z.string(),
price: z.number(),
rating: z.number(),
});
const UIResponseSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("product-grid"),
products: z.array(ProductSchema),
}),
z.object({
type: z.literal("comparison-table"),
products: z.array(ProductSchema),
}),
z.object({
type: z.literal("confirmation"),
message: z.string(),
}),
]);
type UIResponse = z.infer<typeof UIResponseSchema>;
Now your frontend doesn't blindly trust the model.
function safelyRender(data: unknown) {
const result = UIResponseSchema.safeParse(data);
if (!result.success) {
return <FallbackUI />;
}
return <GeneratedUI result={result.data} />;
}
This is one of the biggest differences between a production AI interface and a demo.
A demo asks:
“Can the AI generate UI?”
A production application asks:
“Can the AI generate UI without breaking the application's rules?”
3. Intent Should Drive the Interface
Consider a normal e-commerce search.
Traditional UX:
Search
↓
Filters
↓
Category
↓
Price range
↓
Sort
↓
Results
An intent-driven interface could accept:
"I need running shoes under ₹5,000
for long-distance running."
The application can interpret:
type SearchIntent = {
category: "running-shoes";
maxPrice: 5000;
useCase: "long-distance";
};
The UI can then construct itself:
function SearchExperience({
intent,
}: {
intent: SearchIntent;
}) {
return (
<section>
<IntentSummary intent={intent} />
<ActiveFilters
category={intent.category}
maxPrice={intent.maxPrice}
useCase={intent.useCase}
/>
<ProductGrid
category={intent.category}
maxPrice={intent.maxPrice}
useCase={intent.useCase}
/>
</section>
);
}
The user never had to understand your database schema.
They expressed an outcome.
That's the UX shift.
4. Don't Replace Familiar UI With AI
This is where many AI products get UX wrong.
Not every interaction should become a prompt.
Bad:
┌─────────────────────────────┐
│ What would you like to do? │
│ │
│ [ Ask anything... ] │
└─────────────────────────────┘
Imagine using that for:
- changing a password
- selecting a date
- adjusting volume
- choosing a file
- switching tabs
A traditional control is often faster.
<select>
<option>Monthly</option>
<option>Yearly</option>
</select>
There is no reason to ask AI:
“Could you please change my billing period to yearly?”
UX should therefore follow a simple principle:
Use AI where intent is ambiguous or complex. Use conventional controls where the interaction is obvious.
This hybrid approach is much more powerful.
5. Build Hybrid Interfaces
A modern AI interface might look like:
┌────────────────────────────────────────┐
│ Search │
│ │
│ "Find customers who haven't ordered │
│ in the last 90 days" │
│ │
│ [ Generate filter ] │
└────────────────────────────────────────┘
Generated filters:
[ Last order > 90 days ] [ Customers ]
Results: 438 customers
┌────────────────────────────────────────┐
│ Customer │ Last order │ Value │
├────────────────────────────────────────┤
│ Rahul │ 112 days │ ₹18,400 │
│ Ananya │ 97 days │ ₹12,900 │
└────────────────────────────────────────┘
The AI handles the difficult part.
The resulting UI remains conventional.
That's a much better UX than forcing everything through conversation.
6. Streaming Changes AI UX
AI responses don't arrive instantly.
Waiting for a complete response creates a dead interface.
Instead, stream useful information as soon as it becomes available.
A simplified client implementation:
"use client";
import { useState } from "react";
export function AIQuery() {
const [text, setText] = useState("");
const [result, setResult] = useState("");
async function submit() {
const response = await fetch("/api/query", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ query: text }),
});
if (!response.body) return;
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
setResult((current) =>
current + decoder.decode(value)
);
}
}
return (
<div>
<input
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button onClick={submit}>
Search
</button>
<div>{result}</div>
</div>
);
}
But raw text streaming isn't always the best experience.
A better architecture streams structured UI states.
type StreamEvent =
| {
type: "status";
message: string;
}
| {
type: "ui";
component: UIComponent;
data: unknown;
}
| {
type: "error";
message: string;
};
Then the UI can transition naturally:
Thinking...
↓
Searching products...
↓
Found 24 products
↓
Product Grid
This feels substantially more responsive than:
Loading...
Loading...
Loading...
Loading...
Here is everything.
7. AI Interfaces Need Real Loading States
AI introduces new intermediate states.
Your interface might need:
type Status =
| "idle"
| "understanding"
| "searching"
| "generating"
| "complete"
| "error";
Then:
function StatusMessage({ status }: { status: Status }) {
const messages = {
idle: "",
understanding: "Understanding your request…",
searching: "Searching your data…",
generating: "Preparing your results…",
complete: "Done",
error: "Something went wrong.",
};
return (
<p role="status" aria-live="polite">
{messages[status]}
</p>
);
}
Notice the accessibility detail:
aria-live="polite"
AI applications constantly update content asynchronously, so accessibility cannot be an afterthought.
Modern frontend guidance increasingly treats accessibility as a foundational requirement rather than a final QA step.
8. Optimistic UI Still Matters
AI doesn't mean everything has to wait for the model.
Suppose the user says:
“Add this product to my wishlist.”
You don't necessarily need to wait for an AI response before updating the UI.
async function addToWishlist(productId: string) {
setWishlist((current) => [
...current,
productId,
]);
try {
await fetch("/api/wishlist", {
method: "POST",
body: JSON.stringify({ productId }),
});
} catch {
setWishlist((current) =>
current.filter((id) => id !== productId)
);
}
}
The interface responds immediately.
The network catches up afterward.
That's still good frontend engineering.
AI should not become an excuse for slow interfaces.
9. Use Modern CSS to Make Generated UI Flexible
Generated interfaces can contain unpredictable content.
That means fixed layouts become dangerous.
Instead of:
.card {
width: 300px;
height: 180px;
}
prefer resilient layouts:
.card {
width: min(100%, 24rem);
min-height: 10rem;
padding: 1rem;
}
And use container queries where appropriate:
.card {
container-type: inline-size;
}
@container (min-width: 30rem) {
.card {
display: grid;
grid-template-columns: 1fr auto;
}
}
This is especially useful for component-driven interfaces because the component responds to its actual available space rather than the viewport alone.
Modern frontend discussions increasingly emphasize container queries, :has(), CSS nesting, View Transitions, and other browser-native capabilities.
10. :has() Makes State-Aware UI Easier
For example:
.form-group:has(input:invalid) {
border-color: crimson;
}
Or:
.card:has(.badge) {
padding-top: 2.5rem;
}
This lets CSS respond to DOM state without adding another JavaScript state variable.
The result:
Less JavaScript
↓
Less state
↓
Simpler components
↓
Less synchronization work
The best AI-generated frontend code isn't necessarily the code with the most abstraction.
It's often the code that lets the browser do more of the work.
11. Motion Should Explain State Changes
AI interfaces have a lot of state transitions.
For example:
Prompt
↓
Thinking
↓
Tool execution
↓
Results
↓
Generated UI
Motion can communicate those transitions.
.result {
view-transition-name: result;
}
@keyframes result-enter {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
But motion should remain optional:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
The goal isn't:
“Make the AI UI look futuristic.”
The goal is:
“Make state changes understandable.”
Functional motion is increasingly being treated as part of interface architecture rather than merely decoration.
12. Give Users Control Over AI Actions
Agentic interfaces create a new UX problem:
How much autonomy should the AI have?
Consider:
User:
"Book the cheapest flight."
Should the agent immediately purchase a ticket?
Probably not.
Instead:
AI finds flight
↓
AI displays recommendation
↓
User reviews
↓
User confirms
↓
Purchase
Represent that in the interface:
function BookingConfirmation({
flight,
onConfirm,
}: {
flight: Flight;
onConfirm: () => void;
}) {
return (
<aside>
<h2>Ready to book</h2>
<p>
{flight.airline} · {flight.price}
</p>
<button onClick={onConfirm}>
Confirm booking
</button>
</aside>
);
}
The AI can prepare the action.
The user remains the authority.
This distinction becomes increasingly important as interfaces move from assistants toward agents.
13. Every Agentic Action Needs an Escape Hatch
Imagine:
AI:
"I changed your dashboard layout."
Good UX:
[ Undo ]
Even better:
function ActionToast({
message,
onUndo,
}: {
message: string;
onUndo: () => void;
}) {
return (
<div role="status">
<span>{message}</span>
<button onClick={onUndo}>
Undo
</button>
</div>
);
}
For destructive actions:
<button
onClick={() => setShowConfirmation(true)}
>
Delete project
</button>
Don't allow an autonomous system to turn reversible mistakes into irreversible ones.
14. Design Systems Become More Important, Not Less
There is a misconception that AI makes design systems less important.
The opposite is closer to reality.
If AI can generate hundreds of interface variations, you need stronger constraints.
For example:
const tokens = {
color: {
background: "var(--color-background)",
foreground: "var(--color-foreground)",
accent: "var(--color-accent)",
},
radius: {
sm: "0.375rem",
md: "0.625rem",
lg: "1rem",
},
spacing: {
sm: "0.5rem",
md: "1rem",
lg: "1.5rem",
},
};
Then:
function AIButton({
children,
}: {
children: React.ReactNode;
}) {
return (
<button className="button">
{children}
</button>
);
}
The model should compose existing primitives:
AI
│
├── Button
├── Card
├── Table
├── Dialog
├── Form
└── Navigation
rather than inventing:
AI
│
├── random-blue-button
├── weird-radius-card
├── inconsistent-modal
└── mystery-navigation
This is why design-system quality becomes an AI scalability problem.
15. AI-Generated UI Needs Visual Testing
Traditional tests might check:
expect(screen.getByText("Checkout")).toBeVisible();
But generated UI introduces another question:
Does the interface still look coherent when the content changes?
For example, test long text:
const longTitle =
"This is an unusually long product title that should not destroy the layout";
render(
<ProductCard
title={longTitle}
/>
);
Test empty state:
render(
<SearchResults products={[]} />
);
Test errors:
render(
<SearchResults
error="Unable to load products"
/>
);
Test accessibility:
expect(
screen.getByRole("button", {
name: /confirm booking/i,
})
).toBeVisible();
The important shift is from testing only:
"Does the component work?"
to testing:
"Does the component remain usable
across the states an AI system can produce?"
16. Don't Let AI Become the Performance Bottleneck
An AI-powered frontend can easily become slower than the application it was supposed to improve.
A bad architecture:
Browser
↓
Large JavaScript bundle
↓
AI SDK
↓
Client-side orchestration
↓
API
↓
AI model
↓
API
↓
Browser
A better architecture often looks like:
Browser
↓
Minimal client JavaScript
↓
Server
↓
AI / tools / database
↓
Stream useful result
↓
Browser
Server-first architectures and React Server Components are part of the broader movement toward sending less unnecessary JavaScript to the client.
For example:
export default async function ProductPage() {
const products = await getProducts();
return (
<main>
<ProductGrid products={products} />
<AIProductAssistant />
</main>
);
}
Only the interactive assistant needs to be client-side.
17. Separate Intelligence From Presentation
This is perhaps the most important architectural principle.
Don't do this:
function AIComponent() {
// AI call
// business logic
// database call
// validation
// UI generation
// styling
// analytics
// error handling
}
Instead:
AI Layer
↓
Intent
↓
Application Layer
↓
Validated Data
↓
UI Layer
For example:
type UserIntent = {
action: "find_products";
category?: string;
maxPrice?: number;
};
Then:
async function resolveIntent(
intent: UserIntent
) {
return database.products.findMany({
where: {
category: intent.category,
price: {
lte: intent.maxPrice,
},
},
});
}
And finally:
<ProductGrid products={products} />
This architecture makes the AI replaceable.
You could change the model without rewriting the frontend.
18. A Practical Generative UI Resolver
Putting the ideas together:
type UIResult =
| {
type: "products";
data: Product[];
}
| {
type: "comparison";
data: Product[];
}
| {
type: "confirmation";
data: {
message: string;
};
};
function UIResolver({
result,
}: {
result: UIResult;
}) {
switch (result.type) {
case "products":
return (
<ProductGrid
products={result.data}
/>
);
case "comparison":
return (
<ComparisonTable
products={result.data}
/>
);
case "confirmation":
return (
<ConfirmationCard
message={result.data.message}
/>
);
default:
return <FallbackUI />;
}
}
Now the application has a controlled vocabulary of possible experiences.
That's the foundation of production-grade Generative UI.
19. The UX Rule I Keep Coming Back To
AI doesn't automatically make an interface better.
Sometimes it makes a simple interaction worse.
Compare:
Traditional:
[ Delete ]
vs.
AI:
"What would you like me to do?"
"Delete the current item."
"Are you sure?"
"Yes."
"Okay, deleting..."
That's terrible UX.
But consider:
Traditional:
Dashboard
→ Customers
→ Filters
→ Last purchase
→ Greater than
→ 90 days
→ Apply
versus:
"Show customers who haven't purchased
in the last 90 days."
Here AI can dramatically reduce complexity.
So the correct question isn't:
“Can AI do this?”
Ask:
“Does AI reduce the user's cognitive and interaction cost?”
If it doesn't, don't use it.
20. The New Frontend Skill: Designing Constraints
This is where frontend engineering gets particularly interesting.
AI makes producing code cheaper.
That increases the importance of deciding:
- which components are allowed
- which states exist
- which actions require confirmation
- how errors behave
- what accessibility means
- how responsive layouts work
- what the design system permits
- what data the AI can access
- what the user can undo
- where AI should stop
In other words:
Old frontend:
Write more code.
New frontend:
Design better constraints.
The developer increasingly becomes an orchestrator of systems, rather than merely a producer of UI code.
That aligns with the broader movement toward AI-assisted development and structured design-to-code workflows. Figma's 2026 web-development outlook specifically highlights AI-driven workflows, automated design handoffs, server-first performance, agentic interfaces, component-driven layouts, and accessibility.
A Minimal Production Checklist
Before shipping an AI-powered interface, ask:
□ Is AI actually reducing interaction complexity?
□ Can the model only use approved UI components?
□ Is model output schema-validated?
□ Can users understand what the AI is doing?
□ Can users cancel or undo important actions?
□ Are destructive actions confirmed?
□ Are loading and streaming states designed?
□ Are errors understandable?
□ Does the interface work without animation?
□ Does it work with a keyboard?
□ Does it work with a screen reader?
□ Does it work with long AI-generated content?
□ Does it work on small screens?
□ Is unnecessary JavaScript avoided?
□ Is the design system enforced?
□ Can the AI be replaced without rewriting the UI?
If several answers are “no”, the problem probably isn't your AI model.
It's your frontend architecture.
The Future Isn't “AI UI”
The next generation of frontend applications probably won't feel like traditional websites with an AI button bolted onto them.
They'll feel more adaptive.
Instead of:
Screen → Button → Screen → Form → Screen
we'll increasingly see:
Intent
↓
Understanding
↓
Contextual UI
↓
Action
↓
Result
But the best products won't abandon traditional interface design.
They'll combine both.
┌──────────────┐
│ User Intent │
└──────┬───────┘
│
┌──────▼───────┐
│ AI │
└──────┬───────┘
│
Structured
intent
│
▼
┌───────────────┐
│ Design System │
└──────┬────────┘
│
│
┌───────────┼───────────┐
▼ ▼ ▼
Cards Forms Tables
│ │ │
└───────────┼───────────┘
▼
User Action
AI provides the intelligence.
The design system provides the boundaries.
Frontend engineering provides the reliability.
UX design provides the judgment.
And that combination—not another chatbot—is what makes Generative UI genuinely interesting.
Final Thought
The most valuable frontend engineer in an AI-native product won't necessarily be the person who can write the most React code.
It will increasingly be the person who can answer questions like:
What should the AI be allowed to do?
What should remain deterministic?
Which UI should appear for this intent?
What happens when the model is wrong?
Can the user understand, interrupt, and undo the system?
Does this experience actually make the user's job easier?
Those are frontend questions.
They are also UX questions.
And increasingly, they're becoming the same question.
Top comments (1)
The flight example hides the hard part, which is not generating the UI but showing the user what the system decided on their behalf. "Keep the morning ones" has to become a concrete filter - what counts as morning, was the timezone the departure or arrival one - and if the interface renders results without surfacing that interpretation, the user cannot tell a wrong answer from a narrow one. Traditional UI was verbose and clumsy, but every filter chip was also a receipt of the current query state. Generative interfaces that keep working tend to render the parsed intent as editable state rather than just the outcome, so correcting a misread costs one click instead of retyping the whole sentence and hoping.