DEV Community

Cover image for Prompt-Driven UI: Building Production-Ready Login Pages with AgnosticUI & AI
Rob Levin
Rob Levin

Posted on

Prompt-Driven UI: Building Production-Ready Login Pages with AgnosticUI & AI

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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>
    `;
  }
}
Enter fullscreen mode Exit fullscreen mode
// React wrapper is just mapping
export const ReactInput = createComponent({
  tagName: 'ag-input',
  elementClass: AgnosticInput,
  react: React,
  events: {
    onInput: 'input',
    onChange: 'change',
  },
});
Enter fullscreen mode Exit fullscreen mode

Design Token Integration

The CSS uses AgnosticUI's token system, built with Style Dictionary:

// tokens/spacing.json
{
  "space": {
    "4": { "value": "1rem" },
    "8": { "value": "2rem" }
  }
}
Enter fullscreen mode Exit fullscreen mode

Generated CSS:

:where(html) {
  --ag-space-4: 1rem;
  --ag-space-8: 2rem;
}
Enter fullscreen mode Exit fullscreen mode

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

View Screenshots on GitHub

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
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)