Blocks

Form Blocks

Application UI

Complete 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.

ContactForm.vuevue
<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.

Secure checkout

Card processing UI only. Wire this to your payment provider in application code.

PaymentCollection.vuevue
<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.

Controls what this user can view and change.

Used for default dashboards and notifications.

Send invitation email

Email the user a secure setup link after creation.

Send invitation email
NewUserForm.vuevue
<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.

Review needed

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.

Two-factor authentication
AccountSecurity.vuevue
<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>