Overview
LoginLayout provides the responsive page structure. Your application supplies the form, identity provider actions, validation, routing, and authentication service.
Layouts
Simple
A focused credential form centered on a quiet page.
<script setup lang="ts">
import { ref } from "vue";
import { Button, Checkbox, Field, Link, LoginLayout } from "@dicehub/kappa";
const email = ref("");
const password = ref("");
const passwordVisible = ref(false);
const submit = () => { /* Connect your authentication service. */ };
</script>
<template>
<LoginLayout class="login-page" variant="centered" label="Sign in">
<template #brand>Your product</template>
<form @submit.prevent="submit">
<Field.Root required>
<Field.Label>Email or username</Field.Label>
<Field.Input
v-model="email"
type="text"
autocomplete="username"
required
/>
</Field.Root>
<Field.Root required>
<Field.Label>Password</Field.Label>
<div class="password-control">
<Field.Input
v-model="password"
:type="passwordVisible ? 'text' : 'password'"
autocomplete="current-password"
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 name="remember">
<Checkbox.Control />
<Checkbox.Label>Keep me signed in</Checkbox.Label>
</Checkbox.Root>
<Button type="submit" variant="primary" full-width>
Sign in
</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>
Continue with Google
</Button>
<Button type="button" variant="outline" full-width>
Continue with GitHub
</Button>
</div>
</div>
<p>
New to dicehub? <Link href="/sign-up">Create an account</Link>
</p>
<p>
Forgot your <Link href="/forgot-password">password</Link>?
</p>
</form>
<template #footer>
By continuing, I agree to dicehub's
<a href="/terms">terms</a>, <a href="/privacy">privacy policy</a>, and
<a href="/cookies">cookie policy</a>.
</template>
</LoginLayout>
</template>Split Media
A full-height form beside product or organization context.
<script setup lang="ts">
import { ref } from "vue";
import { Button, Checkbox, Field, Link, LinkButton, LoginLayout } from "@dicehub/kappa";
const email = ref("");
const password = ref("");
const passwordVisible = ref(false);
const submit = () => { /* Connect your authentication service. */ };
</script>
<template>
<LoginLayout class="login-page" variant="split" label="Sign in">
<template #brand>Your product</template>
<LinkButton class="sign-up" href="/sign-up" size="lg">
Sign up
</LinkButton>
<form @submit.prevent="submit">
<Field.Root required>
<Field.Label>Email or username</Field.Label>
<Field.Input
v-model="email"
type="text"
autocomplete="username"
required
/>
</Field.Root>
<Field.Root required>
<Field.Label>Password</Field.Label>
<div class="password-control">
<Field.Input
v-model="password"
:type="passwordVisible ? 'text' : 'password'"
autocomplete="current-password"
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 name="remember">
<Checkbox.Control />
<Checkbox.Label>Keep me signed in</Checkbox.Label>
</Checkbox.Root>
<Button type="submit" variant="primary" full-width>
Sign in
</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>
Continue with Google
</Button>
<Button type="button" variant="outline" full-width>
Continue with GitHub
</Button>
</div>
</div>
<p>
New to dicehub? <Link href="/sign-up">Create an account</Link>
</p>
<p>
Forgot your <Link href="/forgot-password">password</Link>?
</p>
</form>
<template #media>
<img src="/login-engine.svg" alt="" />
</template>
<template #footer>
By continuing, I agree to dicehub's
<a href="/terms">terms</a>, <a href="/privacy">privacy policy</a>, and
<a href="/cookies">cookie policy</a>.
</template>
</LoginLayout>
</template>
<style scoped>
.login-page { position: relative; }
.sign-up {
position: absolute;
z-index: 1;
top: 1.75rem;
right: 1rem;
}
</style>Muted Panel
A contained login panel on a muted application surface.
<script setup lang="ts">
import { ref } from "vue";
import { Button, Checkbox, Field, Link, LoginLayout } from "@dicehub/kappa";
const email = ref("");
const password = ref("");
const passwordVisible = ref(false);
const submit = () => { /* Connect your authentication service. */ };
</script>
<template>
<LoginLayout class="login-page" variant="panel" label="Sign in">
<template #brand>Your product</template>
<form @submit.prevent="submit">
<Field.Root required>
<Field.Label>Email or username</Field.Label>
<Field.Input
v-model="email"
type="text"
autocomplete="username"
required
/>
</Field.Root>
<Field.Root required>
<Field.Label>Password</Field.Label>
<div class="password-control">
<Field.Input
v-model="password"
:type="passwordVisible ? 'text' : 'password'"
autocomplete="current-password"
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 name="remember">
<Checkbox.Control />
<Checkbox.Label>Keep me signed in</Checkbox.Label>
</Checkbox.Root>
<Button type="submit" variant="primary" full-width>
Sign in
</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>
Continue with Google
</Button>
<Button type="button" variant="outline" full-width>
Continue with GitHub
</Button>
</div>
</div>
<p>
New to dicehub? <Link href="/sign-up">Create an account</Link>
</p>
<p>
Forgot your <Link href="/forgot-password">password</Link>?
</p>
</form>
<template #footer>
By continuing, I agree to dicehub's
<a href="/terms">terms</a>, <a href="/privacy">privacy policy</a>, and
<a href="/cookies">cookie policy</a>.
</template>
</LoginLayout>
</template>Media Card
A compact two-column card with supporting media on the logical start side.
<script setup lang="ts">
import { ref } from "vue";
import { Button, Checkbox, Field, Link, LoginLayout } from "@dicehub/kappa";
const email = ref("");
const password = ref("");
const passwordVisible = ref(false);
const submit = () => { /* Connect your authentication service. */ };
</script>
<template>
<LoginLayout class="login-page" variant="card" media-side="start" label="Sign in">
<template #brand>Your product</template>
<form @submit.prevent="submit">
<Field.Root required>
<Field.Label>Email or username</Field.Label>
<Field.Input
v-model="email"
type="text"
autocomplete="username"
required
/>
</Field.Root>
<Field.Root required>
<Field.Label>Password</Field.Label>
<div class="password-control">
<Field.Input
v-model="password"
:type="passwordVisible ? 'text' : 'password'"
autocomplete="current-password"
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 name="remember">
<Checkbox.Control />
<Checkbox.Label>Keep me signed in</Checkbox.Label>
</Checkbox.Root>
<Button type="submit" variant="primary" full-width>
Sign in
</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>
Continue with Google
</Button>
<Button type="button" variant="outline" full-width>
Continue with GitHub
</Button>
</div>
</div>
<p>
New to dicehub? <Link href="/sign-up">Create an account</Link>
</p>
<p>
Forgot your <Link href="/forgot-password">password</Link>?
</p>
</form>
<template #media>
<img src="/login-mesh.svg" alt="" />
</template>
<template #footer>
By continuing, I agree to dicehub's
<a href="/terms">terms</a>, <a href="/privacy">privacy policy</a>, and
<a href="/cookies">cookie policy</a>.
</template>
</LoginLayout>
</template>Email Only
A minimal passwordless entry point for email-link authentication.
<script setup lang="ts">
import { ref } from "vue";
import { Button, Checkbox, Field, Link, LoginLayout } from "@dicehub/kappa";
const email = ref("");
const password = ref("");
const passwordVisible = ref(false);
const submit = () => { /* Connect your authentication service. */ };
</script>
<template>
<LoginLayout class="login-page" variant="centered" label="Sign in">
<template #brand>Your product</template>
<form @submit.prevent="submit">
<Field.Root required>
<Field.Label>Email</Field.Label>
<Field.Input
v-model="email"
type="email"
autocomplete="email"
required
/>
</Field.Root>
<Button type="submit" variant="primary" full-width>
Send sign-in link
</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>
Continue with Google
</Button>
<Button type="button" variant="outline" full-width>
Continue with GitHub
</Button>
</div>
</div>
<p>
New to dicehub? <Link href="/sign-up">Create an account</Link>
</p>
</form>
<template #footer>
By continuing, I agree to dicehub's
<a href="/terms">terms</a>, <a href="/privacy">privacy policy</a>, and
<a href="/cookies">cookie policy</a>.
</template>
</LoginLayout>
</template>Neutral Background
A compact credential form on a quiet neutral application surface.
<script setup lang="ts">
import { ref } from "vue";
import { Button, Checkbox, Field, Link, LoginLayout } from "@dicehub/kappa";
const email = ref("");
const password = ref("");
const passwordVisible = ref(false);
const submit = () => { /* Connect your authentication service. */ };
</script>
<template>
<LoginLayout class="login-page" variant="centered" label="Sign in">
<template #brand>Your product</template>
<form @submit.prevent="submit">
<Field.Root required>
<Field.Label>Email or username</Field.Label>
<Field.Input
v-model="email"
type="text"
autocomplete="username"
required
/>
</Field.Root>
<Field.Root required>
<Field.Label>Password</Field.Label>
<div class="password-control">
<Field.Input
v-model="password"
:type="passwordVisible ? 'text' : 'password'"
autocomplete="current-password"
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 name="remember">
<Checkbox.Control />
<Checkbox.Label>Keep me signed in</Checkbox.Label>
</Checkbox.Root>
<Button type="submit" variant="primary" full-width>
Sign in
</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>
Continue with Google
</Button>
<Button type="button" variant="outline" full-width>
Continue with GitHub
</Button>
</div>
</div>
<p>
New to dicehub? <Link href="/sign-up">Create an account</Link>
</p>
<p>
Forgot your <Link href="/forgot-password">password</Link>?
</p>
</form>
<template #footer>
By continuing, I agree to dicehub's
<a href="/terms">terms</a>, <a href="/privacy">privacy policy</a>, and
<a href="/cookies">cookie policy</a>.
</template>
</LoginLayout>
</template>
<style scoped>
.login-page {
--kappa-login-layout-max-inline-size: 17.5rem;
background: #f4f4f4;
}
.login-page :deep([data-slot="login-layout-brand"]) {
inline-size: min(35.625rem, calc(100vw - 2rem));
justify-self: center;
padding-block-end: 1.5625rem;
border-block-end: 1px solid #ebebeb;
}
.social-divider {
margin-block: 2.1875rem 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; }
</style>Installation and Usage
Import LoginLayout from @dicehub/kappa or @dicehub/kappa/blocks/login-layout. Import each form control from its documented component path when you do not use the root package export.
The block fills the small viewport by default. Set --kappa-login-layout-min-block-size only when the layout must live inside another bounded surface.
API and Composition
| Prop | Type | Default | Purpose |
|---|---|---|---|
variant | centered | split | panel | card | centered | Controls the page structure and surface. |
mediaSide | start | end | end | Places media on a logical side for split and card layouts. |
label | string | Sign in | Names the root region for assistive technology. |
| Slot | Purpose |
|---|---|
brand | Product or organization identity. |
default | Login form or another authentication flow. |
media | Optional supporting visual for split and card layouts. |
footer | Legal copy and secondary account links. |
The media slot is not rendered for centered and panel layouts. At narrow widths, split and card media is hidden so the form remains the first task. Logical media placement supports both left-to-right and right-to-left documents.
Accessibility and Security
The examples use native forms, named fields, browser autocomplete values, visible focus, and polite status messages. The password visibility button has an explicit accessible name. Keyboard behavior comes from the composed Kappa controls. Reduced-motion preferences disable the layout entrance and SVG scan motion.
LoginLayout does not store credentials, call an endpoint, or manage a session. Submit credentials only over HTTPS. Keep server errors general, apply rate limits, protect sessions, and use your authentication provider's current guidance.