Build an accessible UI with your AI coding agent.
Turn a product request into a working notification panel. Give your agent Lumen components and focused API guidance, then check the result yourself. Keep the framework you already use.
By Santiago Molina ·
1. Connect the agent and install your adapter.
In an existing Astro or React application, install the matching package with your project's package manager. The commands below use pnpm. Then install the portable skill in the compatible coding tool you use.
pnpm add @santi020k/lumen-astropnpm add @santi020k/lumen-reactnpx skills add santi020k/lumen --skill lumen-uiFollow the MCP connection guide for structured component discovery. Without MCP, your agent can read installed types and documentation. Load styles once at the app boundary; Astro also mounts its progressive runtime once.
---
import '@santi020k/lumen-astro/styles.css'
import UIPrimitives from '@santi020k/lumen-astro/runtime'
---
<html lang="en">
<head><meta charset="utf-8" /><meta name="viewport" content="width=device-width" /><title>Preferences</title></head>
<body><main><h1>Account settings</h1><slot /></main><UIPrimitives /></body>
</html>import '@santi020k/lumen-react/styles.css'
import NotificationPreferences from './NotificationPreferences'
export default function App() {
return <main><h1>Account settings</h1><NotificationPreferences /></main>
}2. Describe the behavior and states.
Name the inputs, actions, and expected feedback. Explicit behavior gives you and your agent a shared way to judge the result. This prompt works with either installed adapter.
Use Lumen UI to build a notification preferences panel in this app's existing framework.
Read the installed package version and the matching component usage before writing code.
Use public Card parts, Stack, Field, Label, Input, Button, and Alert.
The panel title is Notification preferences. Email address starts as [email protected].
Show delivery details toggles the initially hidden text Weekly summaries arrive on Monday.
Keep edited input when details are hidden. Save preferences validates the email and announces
Preferences saved for <email>. Clear that message when the email changes.
This is an in-memory demo: no network request and no saving across reloads.
Use the existing stylesheet, visible keyboard focus, semantic spacing, and a wrapping action row.
Verify the keyboard flow, invalid input, status announcement, and layout at 390px and 1440px.3. Retrieve the relevant component guidance.
Ask the agent to search for the task, read the matching framework usage, and request full source only when the API and example are insufficient. Use shared spacing and color roles so each new screen follows the same design language.
Components provide presentation and interaction contracts. Your application still owns validation, persistence, authorization, and business rules.
4. Try the reference implementation.
Edit the email, reveal and hide the details, then save. This tested reference implements the prompt with synthetic data and keeps changes in this page only.
Notification preferences
---
import { Alert, Button, Card, CardContent, CardHeader, CardTitle, Field, Input, Label, Stack } from '@santi020k/lumen-astro'
---
<Card as="section" aria-labelledby="ai-preferences-title">
<CardHeader>
<CardTitle as="h2" id="ai-preferences-title">Notification preferences</CardTitle>
</CardHeader>
<CardContent>
<form id="ai-preferences-form">
<Stack gap="group">
<Field>
<Label for="ai-preferences-email">Email address</Label>
<Input id="ai-preferences-email" name="email" required type="email" value="[email protected]" />
</Field>
<Stack direction="horizontal" gap="related" wrap>
<Button aria-controls="ai-delivery-details" aria-expanded="false" id="ai-details-toggle" type="button" variant="outline">
Show delivery details
</Button>
<Button type="submit">Save preferences</Button>
</Stack>
<p hidden id="ai-delivery-details">Weekly summaries arrive on Monday.</p>
<Alert hidden id="ai-preferences-result" role="status" variant="success" />
</Stack>
</form>
</CardContent>
</Card>
<script>
const initializePreferences = () => {
const form = document.querySelector<HTMLFormElement>('#ai-preferences-form')
if (!form || form.dataset.preferencesBound === 'true') return
const toggle = form.querySelector<HTMLButtonElement>('#ai-details-toggle')
const details = form.querySelector<HTMLElement>('#ai-delivery-details')
const result = form.querySelector<HTMLElement>('#ai-preferences-result')
if (!toggle || !details || !result) return
form.dataset.preferencesBound = 'true'
toggle.addEventListener('click', () => {
details.hidden = !details.hidden
toggle.setAttribute('aria-expanded', String(!details.hidden))
})
form.addEventListener('submit', event => {
event.preventDefault()
const email = new FormData(form).get('email')
if (typeof email !== 'string') return
result.textContent = `Preferences saved for ${email}.`
result.hidden = false
})
form.addEventListener('input', () => {
result.hidden = true
})
}
initializePreferences()
document.addEventListener('astro:page-load', initializePreferences)
</script>import { useId, useState } from 'react'
import { Alert, Button, Card, CardContent, CardHeader, CardTitle, Field, Input, Label, Stack } from '@santi020k/lumen-react'
export default function NotificationPreferences() {
const id = useId()
const [email, setEmail] = useState('[email protected]')
const [details, setDetails] = useState(false)
const [savedEmail, setSavedEmail] = useState<string | null>(null)
return (
<Card as="section" aria-labelledby={`${id}-title`}>
<CardHeader>
<CardTitle as="h2" id={`${id}-title`}>Notification preferences</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={event => {
event.preventDefault()
setSavedEmail(email)
}}
>
<Stack gap="group">
<Field>
<Label htmlFor={`${id}-email`}>Email address</Label>
<Input
id={`${id}-email`}
name="email"
required
type="email"
value={email}
onChange={event => {
setEmail(event.target.value)
setSavedEmail(null)
}}
/>
</Field>
<Stack direction="horizontal" gap="related" wrap>
<Button
aria-controls={`${id}-details`}
aria-expanded={details}
onClick={() => {
setDetails(!details)
}}
type="button"
variant="outline"
>
Show delivery details
</Button>
<Button type="submit">Save preferences</Button>
</Stack>
<p hidden={!details} id={`${id}-details`}>Weekly summaries arrive on Monday.</p>
<Alert hidden={savedEmail === null} role="status" variant="success">
{savedEmail === null ? '' : `Preferences saved for ${savedEmail}.`}
</Alert>
</Stack>
</form>
</CardContent>
</Card>
)
}5. Verify before connecting real data.
- Use Tab and Enter to operate both actions; confirm focus stays visible.
- Try an empty or invalid email. The browser should prevent submission.
- Hide and reopen the details. Your edited email should remain.
- Save, then edit again. The previous success message should clear.
- Check the status announcement with a screen reader and inspect narrow and desktop layouts.
For a real save operation, add pending, failure, and retry behavior around your existing application API. See form integrations for the next step.