Imagine generating a complete, production-ready UI across React, Vue, and Lit with a single AI prompt—eliminating framework lock-in and design drift for good. Read on…
Over the past week, I’ve been heads-down building AgnosticUI’s first Login Playbook. It’s an AI-optimized template designed to leverage our framework-agnostic architecture to spit out fully responsive login layouts in seconds. Whether you’re leaning into a high-speed "vibe coding" session or executing a disciplined design system rollout, this playbook gives you a production-ready foundation that bridges the gap between raw AI generation and hand-crafted code.
I can already hear the sighs: "Great, another cookie-cutter login page, dude." Right then. Well, it IS just a starter template—the whole point is for you to…
Imagine generating a complete, production-ready UI across React, Vue, and Lit with a single AI prompt—eliminating framework lock-in and design drift for good. Read on…
Over the past week, I’ve been heads-down building AgnosticUI’s first Login Playbook. It’s an AI-optimized template designed to leverage our framework-agnostic architecture to spit out fully responsive login layouts in seconds. Whether you’re leaning into a high-speed "vibe coding" session or executing a disciplined design system rollout, this playbook gives you a production-ready foundation that bridges the gap between raw AI generation and hand-crafted code.
I can already hear the sighs: "Great, another cookie-cutter login page, dude." Right then. Well, it IS just a starter template—the whole point is for you to tear it apart and make it yours!
Most AI-generated UI today results in what I’d call "AI Slop": a mess of React, Shadcn, and Tailwind that gets shoved down your throat whether you want it or not. Am I the only one who actually wants a choice in their tech stack? I certainly hope not!
In any event, what you get is cookie-cutter generic output that has a "me too" look and doesn’t establish any semblance of design system worthy code. And worse, as you continue to add pages: Styling is inconsistent across prompts. Accessibility is ignored. And you’re locked into one framework. Always. React.
Well, maybe you’re more of a nitpicky dev like me. Maybe you’d like to have some sort of component library awareness, design token based CSS custom properties and design system constraints, cross-framework compatibility from day one, and at minimum passing accessibility.
Enter the Playbook Pattern
A Playbook is a structured prompt template that gives an LLM everything it needs to generate consistent, high-quality UI. It really shouldn’t matter if you use Sonnet/Opus 4.5, Gemini, etc., within reason.
Here’s what goes in:
Component Inventory - Available components and their props Visual Hierarchy - Layout structure and element order Responsive Breakpoints - Mobile, tablet, desktop behaviors Styling Constraints - Tokens, variants, and spacing rules Framework Scaffolding - How to structure the output code
The Login Playbook Structure
# Component Hierarchy
1. **Logo** - Brand identifier (The boilerplate has Ag with blue 'A' because that's AgnosticUI's demo. Of course you will replace this!)
2. **Title** - "Welcome back!" heading
3. **Email Field** - Input with mail icon addon
4. **Password Field** - Input with lock icon addon
5. **Remember/Forgot Row** - Checkbox + link
6. **Primary CTA** - Full-width login button
7. **Social Auth** - Facebook and Google buttons
_All the above can be swapped e.g. you can remove Facebook for Apple or whatever._
**Layout Constraints:**
- Mobile: Single column, full width
- Tablet: Centered card (400px max)
- Desktop: 40/60 split (form left, hero image right)
**Component Variants:**
- Button (Login): `variant="monochrome" shape="rounded"`
- Button (Social): `bordered shape="rounded"`
- Input: `rounded` with left addon slots
Again, this is a starter template. You can update the core design tokens for a radically different theme, use a different AgnosticUI Button variant, and obviously replace the hero image. OMG, please replace that!
The Generated Code
React Implementation
import React, { useState } from 'react';
import { Mail, Lock } from 'lucide-react';
import { ReactButton } from 'agnosticui-core/button/react';
import { ReactInput } from 'agnosticui-core/input/react';
import { ReactCheckbox } from 'agnosticui-core/checkbox/react';
import { ReactLink } from 'agnosticui-core/link/react';
export function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [remember, setRemember] = useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log({ email, password, remember });
};
return (
<form onSubmit={handleSubmit} className="login-form">
<div className="logo">Ag</div>
<h1>Welcome back!</h1>
<ReactInput
label="Email"
type="email"
placeholder="Enter your email"
value={email}
onInput={(e) => setEmail(e.target.value)}
rounded
required
>
<Mail
slot="addon-left"
size={18}
style={{ color: 'var(--ag-text-secondary)' }}
/>
</ReactInput>
<ReactInput
label="Password"
type="password"
placeholder="Enter your password"
value={password}
onInput={(e) => setPassword(e.target.value)}
rounded
required
>
<Lock
slot="addon-left"
size={18}
style={{ color: 'var(--ag-text-secondary)' }}
/>
</ReactInput>
<div className="remember-forgot">
<ReactCheckbox
checked={remember}
onChange={(e) => setRemember(e.target.checked)}
label="Remember me"
/>
<ReactLink href="#forgot">Forgot password</ReactLink>
</div>
<ReactButton
type="submit"
variant="monochrome"
shape="rounded"
isFullWidth
>
Login
</ReactButton>
<div className="divider">or</div>
<ReactButton
bordered
shape="rounded"
isFullWidth
onClick={() => console.log('Facebook login')}
>
<Facebook size={18} /> Facebook
</ReactButton>
<ReactButton
bordered
shape="rounded"
isFullWidth
onClick={() => console.log('Google login')}
>
<img src="/google-icon.svg" alt="" /> Google
</ReactButton>
<div className="signup-prompt">
Don't have an account? <ReactLink href="#signup">Sign up</ReactLink>
</div>
</form>
);
}
The Responsive Layout
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: var(--ag-space-4);
}
/* Mobile: Full width, single column */
.login-form {
width: 100%;
max-width: 400px;
display: flex;
flex-direction: column;
gap: var(--ag-space-4);
}
/* Tablet: Centered card */
@media (min-width: 768px) {
.login-container {
background: var(--ag-bg-secondary);
}
.login-form {
padding: var(--ag-space-8);
background: var(--ag-bg-primary);
border-radius: var(--ag-radius-lg);
box-shadow: var(--ag-elevation-3);
}
}
/* Desktop: Split layout with hero image */
@media (min-width: 1200px) {
.login-container {
display: grid;
grid-template-columns: 2fr 3fr; /* 40% form / 60% hero */
padding: 0;
}
.login-column-left {
display: flex;
align-items: center;
justify-content: center;
padding: var(--ag-space-8);
}
.login-column-right {
background: url('/hero-mountain.jpg') center/cover;
min-height: 100vh;
}
}
.logo {
font-size: 2rem;
font-weight: 600;
margin-bottom: var(--ag-space-2);
}
.logo::first-letter {
color: var(--ag-primary);
}
.divider {
display: flex;
align-items: center;
gap: var(--ag-space-4);
color: var(--ag-text-secondary);
text-align: center;
}
.divider::before,
.divider::after {
content: '';
flex: 1;
border-top: 1px solid var(--ag-border-color);
}
.remember-forgot {
display: flex;
justify-content: space-between;
align-items: center;
}
.signup-prompt {
text-align: center;
color: var(--ag-text-secondary);
}
Why This Works
Framework-Agnostic Core
The same prompt generates Vue and Lit versions because AgnosticUI components are built on Web Components (Lit) with framework wrappers:
// The core logic exists once in Lit
export class AgnosticInput extends LitElement {
@property({ type: String }) label = '';
@property({ type: String }) type = 'text';
@property({ type: Boolean, reflect: true }) rounded = false;
render() {
return html`
<div class="input-wrapper">
<label>${this.label}</label>
<div class="input-container">
<slot name="addon-left"></slot>
<input type="${this.type}" />
<slot name="addon-right"></slot>
</div>
</div>
`;
}
}
// React wrapper is just mapping
export const ReactInput = createComponent({
tagName: 'ag-input',
elementClass: AgnosticInput,
react: React,
events: {
onInput: 'input',
onChange: 'change',
},
});
Design Token Integration
The CSS uses AgnosticUI’s token system, built with Style Dictionary:
// tokens/spacing.json
{
"space": {
"4": { "value": "1rem" },
"8": { "value": "2rem" }
}
}
Generated CSS:
:where(html) {
--ag-space-4: 1rem;
--ag-space-8: 2rem;
}
Have a look at the theme-registry source code to get a feel for how the design tokens are set up, or check out the resulting styles.
Consistent spacing across all generated layouts.
Accessibility Built-In
Because we’re using semantic components with proper ARIA, form labels are properly associated, focus management works, keyboard navigation is handled, and screen readers get the context they need.
No extra prompt engineering required for a11y.
The Developer Experience
Copy the PROMPT.md into Claude, ChatGPT, or your preferred LLM with instructions to create a login page.
The LLM generates:
LoginForm.tsx(React)LoginForm.vue(Vue)LoginForm.lit.ts(Lit)
All using the same components, all maintaining design consistency. Obviously, you’re free to delete the generated framework components you don’t want (but it IS neat to see them all produced, no?). Or, you could use the Lit-based LoginForm.lit.ts one for Svelte, Solid, Preact, Angular, etc., with a bit of tweaking since, ultimately, that’s just web components.
Customize Without Breaking Things
Because it’s using a real component library, you can swap button variants (primary, secondary, monochrome), adjust spacing with tokens (--ag-space-*), add new fields using the same Input component, and change responsive breakpoints.
The AI gave you a starting point. Your design system keeps you consistent.
Beyond Login - The Playbook Strategy
We’re building playbooks for common UI patterns:
Current Playbooks:
- Login/Signup flows ✓
Upcoming playbook ideas (would love your feedback on which ones we should tackle):
- Dashboard layouts
- E-commerce checkout
- Settings panels
Each playbook will include component mapping, responsive strategies, and accessibility requirements with a disclaimer that these will be "dumb UI layouts".
Screenshots
Mobile (375×812): Vertical stack, thumb-friendly buttons Tablet (768×1024): Centered card with elevation Desktop (1440×1080): Split hero layout with 40/60 grid
AI + Design Systems
AI code generation becomes useful when constrained by a solid design system. It’s early, but I’ve started building a Figma and used it to build the Login page template: https://github.com/AgnosticUI/agnosticui/blob/master/v2/graphics/AgnosticUI-2.fig
There are only 5 components so far, but given demand and/or community support the hope is that this Figma will eventually grow to reflect the 50+ components available in the UI kit.
Without constraints: Every prompt gives different styling, accessibility is an afterthought, and framework migration means rewriting from scratch.
With AgnosticUI Playbooks: Consistent design language, built-in accessibility, framework portability, and production-ready output.
Try it yourself:
# Get started in under 2 minutes
npx agnosticui-cli init
Explore the Login Playbook: https://github.com/AgnosticUI/agnosticui/tree/master/v2/playbooks/login
What UI patterns should we build playbooks for next? Drop a comment below.
About AgnosticUI
AgnosticUI is an open-source component library that works across React, Vue, Svelte, Angular, and vanilla JavaScript. Built on Web Components with framework wrappers, it lets you write once and deploy everywhere.
GitHub: https://github.com/AgnosticUI/agnosticui Docs: https://www.agnosticui.com