Overview
LoginLayout also provides the responsive page structure for account creation. The application supplies validation, account checks, provider actions, routing, and the signup service.
| Kappa component | Purpose |
|---|---|
LoginLayout | Centered, split, panel, and media-card authentication structures. |
| Field | Names, email address, username, and password inputs. |
| Checkbox | Required agreement to account policies. |
| Button | Submit and identity-provider actions. |
| Link | Namespace previews, policies, and sign-in navigation. |
Layouts
Complete Account
The full dicehub account form with identity, namespace, password, and consent.
Great! You decided to sign up.
<script setup lang="ts">
import { ref } from "vue";
import { Button, Checkbox, Field, Link, LoginLayout } from "@dicehub/kappa";
const fullName = ref("");
const email = ref("");
const username = ref("");
const password = ref("");
const passwordVisible = ref(false);
const agreed = ref(false);
const submit = () => { /* Connect your account service. */ };
</script>
<template>
<LoginLayout class="signup-page" variant="centered" label="Sign up">
<template #brand>Your product</template>
<h1>Great! You decided to sign up.</h1>
<form @submit.prevent="submit">
<Field.Root required>
<Field.Label>Full name</Field.Label>
<Field.Input v-model="fullName" autocomplete="name" required />
<Field.HelperText>Your name identifies you to collaborators.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Email address</Field.Label>
<Field.Input v-model="email" type="email" autocomplete="email" required />
<Field.HelperText>Used for authentication and account notifications.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Username</Field.Label>
<Field.Input v-model="username" autocomplete="username" required />
<Field.HelperText>Your username becomes your personal project namespace.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Password</Field.Label>
<div class="password-control">
<Field.Input
v-model="password"
:type="passwordVisible ? 'text' : 'password'"
autocomplete="new-password"
minlength="8"
required
/>
<button
type="button"
:aria-label="passwordVisible ? 'Hide password' : 'Show password'"
:aria-pressed="passwordVisible"
@click="passwordVisible = !passwordVisible"
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<template v-if="passwordVisible">
<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" />
<path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" />
<path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" />
<path d="m2 2 20 20" />
</template>
<template v-else>
<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" />
<circle cx="12" cy="12" r="3" />
</template>
</svg>
</button>
</div>
</Field.Root>
<Checkbox.Root v-model:checked="agreed" name="terms" required>
<Checkbox.Control />
<Checkbox.Label>
By signing up, I agree to the terms, privacy policy, and cookie policy.
</Checkbox.Label>
</Checkbox.Root>
<Button type="submit" variant="primary" full-width>
Sign up
</Button>
<div class="social-providers">
<div class="social-divider" aria-hidden="true">
<span>Or continue with</span>
</div>
<div class="social-buttons">
<Button type="button" variant="outline" full-width>Sign in with Google</Button>
<Button type="button" variant="outline" full-width>Sign in with GitHub</Button>
</div>
</div>
<p>Already have an account? <Link href="/sign-in">Sign in</Link></p>
</form>
</LoginLayout>
</template>
<style scoped>
.signup-page { min-block-size: 100svh; }
.signup-page {
--kappa-login-layout-max-inline-size: 35.625rem;
background: #f4f4f4;
}
.signup-page form {
max-inline-size: 25rem;
margin-inline: auto;
}
.signup-page h1 { color: var(--kappa-dicehub-logo-ink, #333333); }
.signup-page h1 { margin: 0 0 0.875rem; text-align: center; }
.social-providers { max-inline-size: 17.5rem; margin: 2.1875rem auto 0; }
.social-divider {
margin-block-end: 1.5625rem;
overflow: hidden;
color: var(--kappa-subtle);
text-align: center;
}
.social-divider span { position: relative; display: inline-block; }
.social-divider span::before,
.social-divider span::after {
position: absolute;
top: 50%;
width: 12.5rem;
height: 1px;
background: var(--kappa-line);
content: "";
}
.social-divider span::before { left: calc(100% + 0.625rem); }
.social-divider span::after { right: calc(100% + 0.625rem); }
.social-buttons { display: grid; gap: 1rem; }
.password-control { position: relative; }
.sign-in { position: absolute; z-index: 1; inset-block-start: 1.75rem; inset-inline-end: 1rem; }
</style>Split Media
The complete form beside a technical product visual and a direct sign-in action.
<script setup lang="ts">
import { ref } from "vue";
import { Button, Checkbox, Field, Link, LinkButton, LoginLayout } from "@dicehub/kappa";
const fullName = ref("");
const email = ref("");
const username = ref("");
const password = ref("");
const passwordVisible = ref(false);
const agreed = ref(false);
const submit = () => { /* Connect your account service. */ };
</script>
<template>
<LoginLayout class="signup-page" variant="split" label="Sign up">
<template #brand>Your product</template>
<LinkButton class="sign-in" href="/sign-in">Sign in</LinkButton>
<h1>Great! You decided to sign up.</h1>
<form @submit.prevent="submit">
<Field.Root required>
<Field.Label>Full name</Field.Label>
<Field.Input v-model="fullName" autocomplete="name" required />
<Field.HelperText>Your name identifies you to collaborators.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Email address</Field.Label>
<Field.Input v-model="email" type="email" autocomplete="email" required />
<Field.HelperText>Used for authentication and account notifications.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Username</Field.Label>
<Field.Input v-model="username" autocomplete="username" required />
<Field.HelperText>Your username becomes your personal project namespace.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Password</Field.Label>
<div class="password-control">
<Field.Input
v-model="password"
:type="passwordVisible ? 'text' : 'password'"
autocomplete="new-password"
minlength="8"
required
/>
<button
type="button"
:aria-label="passwordVisible ? 'Hide password' : 'Show password'"
:aria-pressed="passwordVisible"
@click="passwordVisible = !passwordVisible"
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<template v-if="passwordVisible">
<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" />
<path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" />
<path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" />
<path d="m2 2 20 20" />
</template>
<template v-else>
<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" />
<circle cx="12" cy="12" r="3" />
</template>
</svg>
</button>
</div>
</Field.Root>
<Checkbox.Root v-model:checked="agreed" name="terms" required>
<Checkbox.Control />
<Checkbox.Label>
By signing up, I agree to the terms, privacy policy, and cookie policy.
</Checkbox.Label>
</Checkbox.Root>
<Button type="submit" variant="primary" full-width>
Sign up
</Button>
<div class="social-providers">
<div class="social-divider" aria-hidden="true">
<span>Or continue with</span>
</div>
<div class="social-buttons">
<Button type="button" variant="outline" full-width>Sign in with Google</Button>
<Button type="button" variant="outline" full-width>Sign in with GitHub</Button>
</div>
</div>
<p>Already have an account? <Link href="/sign-in">Sign in</Link></p>
</form>
<template #media>
<img src="/engine-cycle.svg" alt="" />
</template>
</LoginLayout>
</template>
<style scoped>
.signup-page { min-block-size: 100svh; }
.signup-page h1 { margin: 0 0 0.875rem; text-align: center; }
.social-providers { max-inline-size: 17.5rem; margin: 2.1875rem auto 0; }
.social-divider {
margin-block-end: 1.5625rem;
overflow: hidden;
color: var(--kappa-subtle);
text-align: center;
}
.social-divider span { position: relative; display: inline-block; }
.social-divider span::before,
.social-divider span::after {
position: absolute;
top: 50%;
width: 12.5rem;
height: 1px;
background: var(--kappa-line);
content: "";
}
.social-divider span::before { left: calc(100% + 0.625rem); }
.social-divider span::after { right: calc(100% + 0.625rem); }
.social-buttons { display: grid; gap: 1rem; }
.password-control { position: relative; }
.sign-in { position: absolute; z-index: 1; inset-block-start: 1.75rem; inset-inline-end: 1rem; }
</style>Muted Panel
A contained signup panel on a quiet application background.
Great! You decided to sign up.
<script setup lang="ts">
import { ref } from "vue";
import { Button, Checkbox, Field, Link, LoginLayout } from "@dicehub/kappa";
const fullName = ref("");
const email = ref("");
const username = ref("");
const password = ref("");
const passwordVisible = ref(false);
const agreed = ref(false);
const submit = () => { /* Connect your account service. */ };
</script>
<template>
<LoginLayout class="signup-page" variant="panel" label="Sign up">
<template #brand>Your product</template>
<h1>Great! You decided to sign up.</h1>
<form @submit.prevent="submit">
<Field.Root required>
<Field.Label>Full name</Field.Label>
<Field.Input v-model="fullName" autocomplete="name" required />
<Field.HelperText>Your name identifies you to collaborators.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Email address</Field.Label>
<Field.Input v-model="email" type="email" autocomplete="email" required />
<Field.HelperText>Used for authentication and account notifications.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Username</Field.Label>
<Field.Input v-model="username" autocomplete="username" required />
<Field.HelperText>Your username becomes your personal project namespace.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Password</Field.Label>
<div class="password-control">
<Field.Input
v-model="password"
:type="passwordVisible ? 'text' : 'password'"
autocomplete="new-password"
minlength="8"
required
/>
<button
type="button"
:aria-label="passwordVisible ? 'Hide password' : 'Show password'"
:aria-pressed="passwordVisible"
@click="passwordVisible = !passwordVisible"
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<template v-if="passwordVisible">
<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" />
<path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" />
<path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" />
<path d="m2 2 20 20" />
</template>
<template v-else>
<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" />
<circle cx="12" cy="12" r="3" />
</template>
</svg>
</button>
</div>
</Field.Root>
<Checkbox.Root v-model:checked="agreed" name="terms" required>
<Checkbox.Control />
<Checkbox.Label>
By signing up, I agree to the terms, privacy policy, and cookie policy.
</Checkbox.Label>
</Checkbox.Root>
<Button type="submit" variant="primary" full-width>
Sign up
</Button>
<div class="social-providers">
<div class="social-divider" aria-hidden="true">
<span>Or continue with</span>
</div>
<div class="social-buttons">
<Button type="button" variant="outline" full-width>Sign in with Google</Button>
<Button type="button" variant="outline" full-width>Sign in with GitHub</Button>
</div>
</div>
<p>Already have an account? <Link href="/sign-in">Sign in</Link></p>
</form>
</LoginLayout>
</template>
<style scoped>
.signup-page { min-block-size: 100svh; }
.signup-page h1 { margin: 0 0 0.875rem; text-align: center; }
.social-providers { max-inline-size: 17.5rem; margin: 2.1875rem auto 0; }
.social-divider {
margin-block-end: 1.5625rem;
overflow: hidden;
color: var(--kappa-subtle);
text-align: center;
}
.social-divider span { position: relative; display: inline-block; }
.social-divider span::before,
.social-divider span::after {
position: absolute;
top: 50%;
width: 12.5rem;
height: 1px;
background: var(--kappa-line);
content: "";
}
.social-divider span::before { left: calc(100% + 0.625rem); }
.social-divider span::after { right: calc(100% + 0.625rem); }
.social-buttons { display: grid; gap: 1rem; }
.password-control { position: relative; }
.sign-in { position: absolute; z-index: 1; inset-block-start: 1.75rem; inset-inline-end: 1rem; }
</style>Media Card
A compact two-column signup card with contextual workspace media.
Great! You decided to sign up.
<script setup lang="ts">
import { ref } from "vue";
import { Button, Checkbox, Field, Link, LoginLayout } from "@dicehub/kappa";
const fullName = ref("");
const email = ref("");
const username = ref("");
const password = ref("");
const passwordVisible = ref(false);
const agreed = ref(false);
const submit = () => { /* Connect your account service. */ };
</script>
<template>
<LoginLayout class="signup-page" variant="card" media-side="start" label="Sign up">
<template #brand>Your product</template>
<h1>Great! You decided to sign up.</h1>
<form @submit.prevent="submit">
<Field.Root required>
<Field.Label>Full name</Field.Label>
<Field.Input v-model="fullName" autocomplete="name" required />
<Field.HelperText>Your name identifies you to collaborators.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Email address</Field.Label>
<Field.Input v-model="email" type="email" autocomplete="email" required />
<Field.HelperText>Used for authentication and account notifications.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Username</Field.Label>
<Field.Input v-model="username" autocomplete="username" required />
<Field.HelperText>Your username becomes your personal project namespace.</Field.HelperText>
</Field.Root>
<Field.Root required>
<Field.Label>Password</Field.Label>
<div class="password-control">
<Field.Input
v-model="password"
:type="passwordVisible ? 'text' : 'password'"
autocomplete="new-password"
minlength="8"
required
/>
<button
type="button"
:aria-label="passwordVisible ? 'Hide password' : 'Show password'"
:aria-pressed="passwordVisible"
@click="passwordVisible = !passwordVisible"
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<template v-if="passwordVisible">
<path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" />
<path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" />
<path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" />
<path d="m2 2 20 20" />
</template>
<template v-else>
<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" />
<circle cx="12" cy="12" r="3" />
</template>
</svg>
</button>
</div>
</Field.Root>
<Checkbox.Root v-model:checked="agreed" name="terms" required>
<Checkbox.Control />
<Checkbox.Label>
By signing up, I agree to the terms, privacy policy, and cookie policy.
</Checkbox.Label>
</Checkbox.Root>
<Button type="submit" variant="primary" full-width>
Sign up
</Button>
<div class="social-providers">
<div class="social-divider" aria-hidden="true">
<span>Or continue with</span>
</div>
<div class="social-buttons">
<Button type="button" variant="outline" full-width>Sign in with Google</Button>
<Button type="button" variant="outline" full-width>Sign in with GitHub</Button>
</div>
</div>
<p>Already have an account? <Link href="/sign-in">Sign in</Link></p>
</form>
<template #media>
<img src="/workspace-preview.svg" alt="" />
</template>
</LoginLayout>
</template>
<style scoped>
.signup-page { min-block-size: 100svh; }
.signup-page h1 { margin: 0 0 0.875rem; text-align: center; }
.social-providers { max-inline-size: 17.5rem; margin: 2.1875rem auto 0; }
.social-divider {
margin-block-end: 1.5625rem;
overflow: hidden;
color: var(--kappa-subtle);
text-align: center;
}
.social-divider span { position: relative; display: inline-block; }
.social-divider span::before,
.social-divider span::after {
position: absolute;
top: 50%;
width: 12.5rem;
height: 1px;
background: var(--kappa-line);
content: "";
}
.social-divider span::before { left: calc(100% + 0.625rem); }
.social-divider span::after { right: calc(100% + 0.625rem); }
.social-buttons { display: grid; gap: 1rem; }
.password-control { position: relative; }
.sign-in { position: absolute; z-index: 1; inset-block-start: 1.75rem; inset-inline-end: 1rem; }
</style>Email Only
A minimal account entry point for applications that complete registration later.
Create your account
<script setup lang="ts">
import { ref } from "vue";
import { Button, Checkbox, Field, Link, LoginLayout } from "@dicehub/kappa";
const fullName = ref("");
const email = ref("");
const username = ref("");
const password = ref("");
const passwordVisible = ref(false);
const agreed = ref(false);
const submit = () => { /* Connect your account service. */ };
</script>
<template>
<LoginLayout class="signup-page" variant="centered" label="Sign up">
<template #brand>Your product</template>
<h1>Create your account</h1>
<form @submit.prevent="submit">
<Field.Root required>
<Field.Label>Email address</Field.Label>
<Field.Input v-model="email" type="email" autocomplete="email" required />
</Field.Root>
<Button type="submit" variant="primary" full-width>
Create account
</Button>
<div class="social-providers">
<div class="social-divider" aria-hidden="true">
<span>Or continue with</span>
</div>
<div class="social-buttons">
<Button type="button" variant="outline" full-width>Sign in with Google</Button>
<Button type="button" variant="outline" full-width>Sign in with GitHub</Button>
</div>
</div>
<p>Already have an account? <Link href="/sign-in">Sign in</Link></p>
</form>
<template #footer>
By continuing, I agree to the terms, privacy policy, and cookie policy.
</template>
</LoginLayout>
</template>
<style scoped>
.signup-page { min-block-size: 100svh; }
.signup-page h1 { margin: 0 0 0.875rem; text-align: center; }
.social-providers { max-inline-size: 17.5rem; margin: 2.1875rem auto 0; }
.social-divider {
margin-block-end: 1.5625rem;
overflow: hidden;
color: var(--kappa-subtle);
text-align: center;
}
.social-divider span { position: relative; display: inline-block; }
.social-divider span::before,
.social-divider span::after {
position: absolute;
top: 50%;
width: 12.5rem;
height: 1px;
background: var(--kappa-line);
content: "";
}
.social-divider span::before { left: calc(100% + 0.625rem); }
.social-divider span::after { right: calc(100% + 0.625rem); }
.social-buttons { display: grid; gap: 1rem; }
.password-control { position: relative; }
.sign-in { position: absolute; z-index: 1; inset-block-start: 1.75rem; inset-inline-end: 1rem; }
</style>Usage and Composition
Import LoginLayout and the form controls from @dicehub/kappa. Keep username availability, email uniqueness, password policy, consent records, and provider setup in the application.
The complete example uses one full-name field and a fluid form width, so it does not copy the fixed-width overflow of the existing application page.
Accessibility and Security
The examples use native forms, named fields, browser autocomplete values, required controls, visible focus, and polite status messages. The password visibility control has an explicit accessible name. Helper text explains how account data is used.
Validate every value on the server. Check email and username availability without exposing other accounts. Hash passwords with a current password-hashing function, apply rate limits, and send account data only over HTTPS.