ZevAuth
Sign up

Concepts

Customising the components

Every setting, where it lives, and how to override it from code.

Every visual decision has two homes. The console is for people who are not going to open an editor, and it applies everywhere at once. The appearance prop is for decisions that belong in your repository, reviewed with your code and deployed with it.

You do not choose one. Code wins where it speaks, and the console answers everything it leaves out.

<ZevAuthProvider
  publishableKey="pk_live_..."
  appearance={{ accentColor: '#5b3df5', cardStyle: 'flat' }}
>
  <App />
</ZevAuthProvider>

That sets two things. Your logo, your wording, your layout and everything else keep coming from the console.

Where to put it

Put itWhen
The consoleA designer or a founder should be able to change it without a deploy
appearance on <ZevAuthProvider>It belongs in your repository, and every screen should match
appearance on one componentOne screen should differ from the rest

The narrower one wins, so a component’s appearance beats the provider’s, which beats the console.

// Everything matches, except the sign-up page, which is flat.
<ZevAuthProvider publishableKey="pk_live_..." appearance={{ accentColor: '#5b3df5' }}>
  <SignIn />
  <SignUp appearance={{ cardStyle: 'flat' }} />
</ZevAuthProvider>

Everything you can set

Field names are identical in the console and in appearance, so there is nothing to translate.

The brand

FieldValuesWhat it does
accentColorany hexButtons, focus rings, links
colorScheme'system' 'light' 'dark'Follows the device, or forces one
borderRadius'none' 'small' 'medium' 'large'Corners on every control
fontFamily'system' 'inter' 'inherit''inherit' takes your page’s font
logoUrla URLAbove the form, and in every auth email
appearance={{
  accentColor: '#5b3df5',
  borderRadius: 'large',
  fontFamily: 'inherit',
}}

The form itself

FieldValuesWhat it does
cardStyle'card' 'flat''flat' removes the border, background and padding
contentAlign'center' 'top'Where the form sits vertically in its space
showIdentitytrue falseWhether your logo and name appear above the form
showAppNametrue falseWhether your name prints under your logo

Blending into your own page. If you have already designed the panel the form sits in, our card is a box inside a box:

<SignIn appearance={{ cardStyle: 'flat', showIdentity: false }} />

That removes our border, background, padding, logo and name, leaving the fields and buttons to inherit your page.

Sitting under a fixed header. A centred form collides with an app shell’s top bar. Start it at the top instead:

<SignIn appearance={{ contentAlign: 'top' }} />

The words

FieldValuesWhat it does
signInTitletext, or nullnull prints no heading at all
signInSubtitletext, or nullEmpty by default
signUpTitletext, or null
signUpSubtitletext, or null
<SignIn appearance={{ signInTitle: 'Welcome back', signInSubtitle: 'Sign in to continue.' }} />
<SignIn appearance={{ signInTitle: null }} />   {/* no heading */}

This is also how you ship a language we do not.

The layout

FieldValuesWhat it does
signInLayout'centered' 'split_left' 'split_right'Where the form sits on the page
asideStyle'gradient' 'solid' 'image'What the panel beside it is made of
asideColorany hexThe panel colour
asideColorDarkany hexThat colour in dark mode
asideImageUrla URLThe panel banner
asideImageUrlDarka URLThat banner in dark mode
asideHeadingtextOn the panel
asideTexttextUnder the heading

The component prop is layout, spelled with hyphens, because that is what a React prop looks like:

<SignIn layout="split-right" />

Both work, and the prop wins.

The behaviour

FieldValuesWhat it does
passwordResetMode'dedicated' 'inline'Whether the reset gets its own screen

'dedicated' is the default and asks for the address on a screen of its own, prefilled from the sign-in field if something was typed there. 'inline' reuses the sign-in field and refuses when it is empty.

Light and dark

Set colorScheme to 'system' (the default) and every component follows the viewer’s device. The panel beside a split form takes its own dark variant, because a colour or a photograph chosen for a light page is often wrong on a dark one:

appearance={{
  asideStyle: 'gradient',
  asideColor: '#5b3df5',       // light mode
  asideColorDark: '#1a1440',   // dark mode
}}

Leave the dark one out and the light one is used for both.

Writing your own panel? useZevTheme() tells you which one is painting, resolved the same way the components resolve it:

import { SignIn, useZevTheme } from '@zevauth/react';

function Panel() {
  const isDark = useZevTheme();
  return <img src={isDark ? '/hero-dark.jpg' : '/hero-light.jpg'} alt="" />;
}

<SignIn layout="split-right" aside={<Panel />} />

Replacing the panel entirely

aside takes any JSX and replaces the console picture and copy:

<SignIn
  layout="split-right"
  aside={
    <div style={{ padding: 48, color: 'white' }}>
      <blockquote>"We moved 40,000 accounts in an afternoon."</blockquote>
      <cite>Ada Lovelace, CTO</cite>
    </div>
  }
/>

A worked example

An app shell with its own header, its own panel, and its own dark mode:

import { ZevAuthProvider, SignIn, SignUp } from '@zevauth/react';

export function Auth() {
  return (
    <ZevAuthProvider
      publishableKey={import.meta.env.VITE_ZEVAUTH_PUBLISHABLE_KEY}
      appearance={{
        // Our page already has a panel, a logo and a font.
        cardStyle: 'flat',
        showIdentity: false,
        fontFamily: 'inherit',
        contentAlign: 'top',
        accentColor: '#5b3df5',
        signInTitle: 'Welcome back',
        signInSubtitle: 'Sign in to continue.',
      }}
    >
      <MyHeader />
      <main className="my-panel">
        <SignIn secondaryMethods="collapsed" afterSignInUrl="/dashboard" />
      </main>
    </ZevAuthProvider>
  );
}

One thing you cannot set

showPoweredBy is not part of appearance. Whether “Secured by ZevAuth” appears is resolved on the server from your plan, and the merge drops any attempt to set it in code, including from plain JavaScript with no types to stop it.

Removing it is a paid feature, switched off under Customisation → Badge in the console once your plan includes it. Development environments always show it, so you never design against a screen production will not give you.

Updated at, Wednesday, October 21, 2026