Skip to content
Kappa
Login
@dicehub/kappav0.4.2

Login

Responsive login layouts built from Kappa form controls, with working local examples and application-owned authentication.

Overview

LoginLayout provides the responsive page structure. Your application supplies the form, identity provider actions, validation, routing, and authentication service.

Kappa componentPurpose
LoginLayoutCentered, split, panel, and media-card page structures.
FieldAccessible labels and credential inputs.
CheckboxOptional device or session preferences.
ButtonSubmit and identity-provider actions.
LinkRecovery, registration, legal, and support destinations.

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.

Login · neutral-backgroundOpen full example (new tab)
<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

PropTypeDefaultPurpose
variantcentered | split | panel | cardcenteredControls the page structure and surface.
mediaSidestart | endendPlaces media on a logical side for split and card layouts.
labelstringSign inNames the root region for assistive technology.
SlotPurpose
brandProduct or organization identity.
defaultLogin form or another authentication flow.
mediaOptional supporting visual for split and card layouts.
footerLegal 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.