Blocks
Form Blocks
Application UIComplete application form starters using field primitives, form state, native controls, and semantic status tokens.
Lead capture
Contact form
A compact contact form for collecting a name, email, business, phone number, message, and subscription opt-in.
Contact
Start a conversation
A compact lead form with contact details, business context, and a newsletter opt-in.
<script setup>
import { ref } from 'vue';
import { DomButton, DomCard, DomCheckbox, DomEmailInput, DomForm, DomTextInput, DomTextareaInput } from '../../../lib/vue';
const contact = ref({
name: '',
email: '',
business: '',
phone: '',
message: '',
subscribe: true,
});
</script>
<template>
<DomCard padding="lg" class="w-full max-w-2xl">
<div class="border-b border-border pb-6">
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-muted-fg">Contact</p>
<h3 class="mt-2 text-2xl font-semibold tracking-tight">Start a conversation</h3>
<p class="mt-2 max-w-lg text-sm leading-6 text-muted-fg">
A compact lead form with contact details, business context, and a newsletter opt-in.
</p>
</div>
<DomForm v-model="contact" name="contact" class="mt-6 grid gap-5">
<div class="grid gap-5 md:grid-cols-2">
<DomTextInput name="name" label="Name" placeholder="Maya Patel" required autocomplete="name" />
<DomEmailInput name="email" label="Email" placeholder="maya@example.com" required autocomplete="email" />
</div>
<div class="grid gap-5 md:grid-cols-2">
<DomTextInput name="business" label="Business" placeholder="Northstar Studio" autocomplete="organization" />
<DomTextInput name="phone" type="tel" label="Phone" placeholder="+44 7700 900123" autocomplete="tel" />
</div>
<DomTextareaInput
name="message"
label="What can we help with?"
placeholder="Tell us about the project, timeline, or problem."
:rows="4"
/>
<div class="rounded-2xl border border-border bg-secondary/50 p-4">
<DomCheckbox
name="subscribe"
label="Subscribe to product updates"
description="Send occasional notes about DOM Studio, app UI patterns, and release changes."
/>
</div>
<div class="flex flex-wrap justify-end gap-2 border-t border-border pt-6">
<DomButton variant="secondary" type="button">Clear</DomButton>
<DomButton type="submit">Send message</DomButton>
</div>
</DomForm>
</DomCard>
</template>
Checkout
Payment collection form
A payment details layout with card fields, amount, region, and a save-card preference.
Payment
Collect payment
A billing form layout for card details, region, amount, and save-card preference.
<script setup>
import { ref } from 'vue';
import { DomButton, DomCard, DomCheckbox, DomForm, DomNativeSelect, DomNumberInput, DomTextInput } from '../../../lib/vue';
const payment = ref({
amount: 249,
name: 'Maya Patel',
card: '',
expiry: '',
cvc: '',
country: 'gb',
save: true,
});
const countries = [
{ label: 'United Kingdom', value: 'gb' },
{ label: 'United States', value: 'us' },
{ label: 'Ireland', value: 'ie' },
{ label: 'Germany', value: 'de' },
];
</script>
<template>
<DomCard padding="lg" class="w-full max-w-2xl">
<div class="flex flex-wrap items-start justify-between gap-4 border-b border-border pb-6">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-muted-fg">Payment</p>
<h3 class="mt-2 text-2xl font-semibold tracking-tight">Collect payment</h3>
<p class="mt-2 max-w-lg text-sm leading-6 text-muted-fg">
A billing form layout for card details, region, amount, and save-card preference.
</p>
</div>
<span class="rounded-full bg-success/15 px-2.5 py-1 text-xs font-medium text-success">Secure checkout</span>
</div>
<DomForm v-model="payment" name="payment" class="mt-6 grid gap-5">
<div class="grid gap-5 md:grid-cols-[1fr_0.7fr]">
<DomTextInput name="name" label="Name on card" placeholder="Maya Patel" required autocomplete="cc-name" />
<DomNumberInput name="amount" label="Amount" :min="1" :step="1" required />
</div>
<DomTextInput
name="card"
label="Card number"
placeholder="4242 4242 4242 4242"
required
autocomplete="cc-number"
/>
<div class="grid gap-5 md:grid-cols-3">
<DomTextInput name="expiry" label="Expiry" placeholder="MM / YY" required autocomplete="cc-exp" />
<DomTextInput name="cvc" label="CVC" placeholder="123" required autocomplete="cc-csc" />
<DomNativeSelect name="country" label="Country" :options="countries" placeholder="Select country" />
</div>
<div class="rounded-2xl border border-border bg-secondary/50 p-4">
<DomCheckbox
name="save"
label="Save payment method"
description="Store this card for future invoices and subscription renewals."
/>
</div>
<div class="flex flex-wrap items-center justify-between gap-3 border-t border-border pt-6">
<p class="text-xs text-muted-fg">Card processing UI only. Wire this to your payment provider in application code.</p>
<DomButton type="submit">Charge card</DomButton>
</div>
</DomForm>
</DomCard>
</template>
Team admin
Add a new user
A user creation form for assigning role, team, and invite behaviour.
Users
Add a new user
A team admin form for creating a user, assigning access, and choosing whether to send the invite immediately.
<script setup>
import { ref } from 'vue';
import { DomButton, DomCard, DomEmailInput, DomForm, DomNativeSelect, DomTextInput, DomToggle } from '../../../lib/vue';
const user = ref({
name: '',
email: '',
role: 'viewer',
team: 'product',
sendInvite: true,
});
const roles = [
{ label: 'Owner', value: 'owner' },
{ label: 'Administrator', value: 'admin' },
{ label: 'Viewer', value: 'viewer' },
];
const teams = [
{ label: 'Product', value: 'product' },
{ label: 'Design', value: 'design' },
{ label: 'Engineering', value: 'engineering' },
{ label: 'Operations', value: 'operations' },
];
</script>
<template>
<DomCard padding="lg" class="w-full max-w-2xl">
<div class="border-b border-border pb-6">
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-muted-fg">Users</p>
<h3 class="mt-2 text-2xl font-semibold tracking-tight">Add a new user</h3>
<p class="mt-2 max-w-lg text-sm leading-6 text-muted-fg">
A team admin form for creating a user, assigning access, and choosing whether to send the invite immediately.
</p>
</div>
<DomForm v-model="user" name="newUser" class="mt-6 grid gap-5">
<div class="grid gap-5 md:grid-cols-2">
<DomTextInput name="name" label="Full name" placeholder="Alex Morgan" required autocomplete="name" />
<DomEmailInput name="email" label="Email" placeholder="alex@example.com" required autocomplete="email" />
</div>
<div class="grid gap-5 md:grid-cols-2">
<DomNativeSelect
name="role"
label="Role"
description="Controls what this user can view and change."
:options="roles"
/>
<DomNativeSelect
name="team"
label="Team"
description="Used for default dashboards and notifications."
:options="teams"
/>
</div>
<div class="flex items-center justify-between gap-4 rounded-2xl border border-border bg-secondary/50 p-4">
<div>
<p class="text-sm font-medium">Send invitation email</p>
<p class="mt-1 text-sm text-muted-fg">Email the user a secure setup link after creation.</p>
</div>
<DomToggle name="sendInvite" label="Send invitation email" :chrome="false" />
</div>
<div class="flex flex-wrap justify-end gap-2 border-t border-border pt-6">
<DomButton variant="secondary" type="button">Save draft</DomButton>
<DomButton type="submit">Create user</DomButton>
</div>
</DomForm>
</DomCard>
</template>
Starter
Account security form
This shows how richer fields should behave inside a real settings form.
Account
Security settings
A complete form starter with text input, native select, password strength, breach warning, and a toggle.
Used for login, notifications, and account recovery.
Native select is best when the browser picker is preferable on mobile.
Very weak password (Time to crack: instantly)
This password appears in known breaches. Choose a different password.
Try changing this away from password123 to clear the breach warning.
Two-factor authentication
Require a verification code during login.
<script setup>
import { ref } from 'vue';
import { DomButton, DomCard, DomNativeSelect, DomPasswordInput, DomTextInput, DomToggle } from '../../../lib/vue';
const email = ref('steve@example.com');
const password = ref('password123');
const role = ref('admin');
const twoFactor = ref(true);
</script>
<template>
<DomCard padding="lg" class="w-full max-w-2xl">
<div class="flex flex-wrap items-start justify-between gap-4 border-b border-border pb-6">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-muted-fg">Account</p>
<h3 class="mt-2 text-2xl font-semibold tracking-tight">Security settings</h3>
<p class="mt-2 max-w-lg text-sm leading-6 text-muted-fg">
A complete form starter with text input, native select, password strength, breach warning, and a toggle.
</p>
</div>
<span class="rounded-full bg-warning/15 px-2.5 py-1 text-xs font-medium text-warning">Review needed</span>
</div>
<div class="mt-6 grid gap-5">
<DomTextInput
v-model="email"
label="Email address"
description="Used for login, notifications, and account recovery."
placeholder="you@example.com"
/>
<DomNativeSelect
v-model="role"
label="Access level"
description="Native select is best when the browser picker is preferable on mobile."
:options="[
{ label: 'Owner', value: 'owner' },
{ label: 'Administrator', value: 'admin' },
{ label: 'Editor', value: 'editor' },
]"
/>
<DomPasswordInput
v-model="password"
label="Password"
description="Try changing this away from password123 to clear the breach warning."
:compromised="password === 'password123'"
/>
<div class="flex items-center justify-between gap-4 rounded-2xl border border-border bg-secondary/60 p-4">
<div>
<p class="text-sm font-medium">Two-factor authentication</p>
<p class="mt-1 text-sm text-muted-fg">Require a verification code during login.</p>
</div>
<DomToggle v-model="twoFactor" label="Two-factor authentication" />
</div>
</div>
<div class="mt-6 flex flex-wrap justify-end gap-2 border-t border-border pt-6">
<DomButton variant="secondary">Cancel</DomButton>
<DomButton>Save changes</DomButton>
</div>
</DomCard>
</template>