Skip to content
kerfjsGitHub

Cover

Fills the screen and centres one thing, with a header and footer at the edges.

SourcePackage @kerf-js/veneer

Preview

tsx
import { Cover } from '@kerf-js/veneer'export function Parts() {  return (  <Cover style={SCREEN}>    <Box>Header hugs the top</Box>    <Box      data-part="center"      tall={96}    >      data-part="center"    </Box>    <Box>Footer hugs the bottom</Box>  </Cover>  )}type Status =  | 'Live'  | 'Building'  | 'Failed'  | 'Paused'const PROJECTS: {  id: string  name: string  owner: string  region: string  status: Status  deploys: number  uptime: number  latency: number  cost: number  updated: string}[] = [  {    id: 'storefront',    name: 'storefront',    owner: 'Priya Raman',    region: 'eu-west',    status: 'Live',    deploys: 412,    uptime: 99.98,    latency: 84,    cost: 1240,    updated: '2026-10-31',  },  {    id: 'checkout',    name: 'checkout',    owner: 'Tomás Oliveira',    region: 'eu-west',    status: 'Live',    deploys: 288,    uptime: 99.95,    latency: 112,    cost: 980,    updated: '2026-10-31',  },  {    id: 'search-api',    name: 'search-api',    owner: 'Lena Fischer',    region: 'us-east',    status: 'Building',    deploys: 196,    uptime: 99.91,    latency: 146,    cost: 1610,    updated: '2026-10-30',  },  {    id: 'billing',    name: 'billing',    owner: 'Mara Lindqvist',    region: 'us-east',    status: 'Live',    deploys: 154,    uptime: 99.99,    latency: 73,    cost: 720,    updated: '2026-10-29',  },  {    id: 'mobile-bff',    name: 'mobile-bff',    owner: 'Sam Okafor',    region: 'ap-south',    status: 'Failed',    deploys: 233,    uptime: 99.42,    latency: 208,    cost: 860,    updated: '2026-10-29',  },  {    id: 'notifier',    name: 'notifier',    owner: 'Jun Tanaka',    region: 'eu-north',    status: 'Live',    deploys: 97,    uptime: 99.97,    latency: 58,    cost: 310,    updated: '2026-10-27',  },  {    id: 'reports',    name: 'reports',    owner: 'Noor Haddad',    region: 'us-west',    status: 'Paused',    deploys: 41,    uptime: 99.8,    latency: 321,    cost: 190,    updated: '2026-10-22',  },  {    id: 'edge-cache',    name: 'edge-cache',    owner: 'Jun Tanaka',    region: 'global',    status: 'Live',    deploys: 318,    uptime: 100,    latency: 21,    cost: 1430,    updated: '2026-10-31',  },  {    id: 'auth',    name: 'auth',    owner: 'Mara Lindqvist',    region: 'eu-west',    status: 'Live',    deploys: 176,    uptime: 99.99,    latency: 66,    cost: 540,    updated: '2026-10-28',  },  {    id: 'ingest',    name: 'ingest',    owner: 'Lena Fischer',    region: 'us-east',    status: 'Live',    deploys: 264,    uptime: 99.93,    latency: 131,    cost: 2120,    updated: '2026-10-30',  },]const min = 60_000const BOX =  'display:grid;align-content:center;' +  'gap:2px;' +  'padding:var(--veneer-space-related);' +  'border:var(--veneer-border-hairline) ' +  'solid var(--veneer-color-border);' +  'border-radius:' +  'var(--veneer-radius-surface);' +  'background:var(--veneer-color-surface);' +  'color:var(--veneer-color-text);' +  'font-size:var(--veneer-text-caption-size);' +  'min-inline-size:0'const projectOf = (c: unknown) => {  const n = Number(typeof c === 'function' ? c() : c)  return Number.isInteger(n) && n > 0    ? PROJECTS[(n - 1) % PROJECTS.length]    : undefined}function Box({  tall,  children,  ...rest}: {  tall?: number  children?: unknown  [attr: string]: unknown}) {  return (    <div      style={        tall ? `${BOX};min-block-size:${tall}px` : BOX      }      {...rest}    >      {projectOf(children) ? (        <>          <strong>{projectOf(children)!.name}</strong>          <span            style={              'color:var(--veneer-color-text-subtle)'            }          >            {projectOf(children)!.region}          </span>        </>      ) : (        children      )}    </div>  )}const SCREEN =  '--veneer-viewport-block:360px;' +  'border:var(--veneer-border-hairline) ' +  'solid var(--veneer-color-border);' +  'border-radius:' +  'var(--veneer-radius-surface);' +  'padding-inline:var(--veneer-space-group)'

Installation

npm install @kerf-js/veneer

Usage

tsx
import { Cover } from '@kerf-js/veneer'<Cover style={SCREEN}>  <Box>Header hugs the top</Box>  <Box    data-part="center"    tall={96}  >    data-part="center"  </Box>  <Box>Footer hugs the bottom</Box></Cover>

Inside a box

min="fill" fills its parent instead of the screen.

tsx
import { Cover } from '@kerf-js/veneer'export function Fill() {  return (  <div style={FILL}>    <Cover min="fill">      <Box data-part="center">        min="fill" takes its parent's height      </Box>    </Cover>  </div>  )}type Status =  | 'Live'  | 'Building'  | 'Failed'  | 'Paused'const PROJECTS: {  id: string  name: string  owner: string  region: string  status: Status  deploys: number  uptime: number  latency: number  cost: number  updated: string}[] = [  {    id: 'storefront',    name: 'storefront',    owner: 'Priya Raman',    region: 'eu-west',    status: 'Live',    deploys: 412,    uptime: 99.98,    latency: 84,    cost: 1240,    updated: '2026-10-31',  },  {    id: 'checkout',    name: 'checkout',    owner: 'Tomás Oliveira',    region: 'eu-west',    status: 'Live',    deploys: 288,    uptime: 99.95,    latency: 112,    cost: 980,    updated: '2026-10-31',  },  {    id: 'search-api',    name: 'search-api',    owner: 'Lena Fischer',    region: 'us-east',    status: 'Building',    deploys: 196,    uptime: 99.91,    latency: 146,    cost: 1610,    updated: '2026-10-30',  },  {    id: 'billing',    name: 'billing',    owner: 'Mara Lindqvist',    region: 'us-east',    status: 'Live',    deploys: 154,    uptime: 99.99,    latency: 73,    cost: 720,    updated: '2026-10-29',  },  {    id: 'mobile-bff',    name: 'mobile-bff',    owner: 'Sam Okafor',    region: 'ap-south',    status: 'Failed',    deploys: 233,    uptime: 99.42,    latency: 208,    cost: 860,    updated: '2026-10-29',  },  {    id: 'notifier',    name: 'notifier',    owner: 'Jun Tanaka',    region: 'eu-north',    status: 'Live',    deploys: 97,    uptime: 99.97,    latency: 58,    cost: 310,    updated: '2026-10-27',  },  {    id: 'reports',    name: 'reports',    owner: 'Noor Haddad',    region: 'us-west',    status: 'Paused',    deploys: 41,    uptime: 99.8,    latency: 321,    cost: 190,    updated: '2026-10-22',  },  {    id: 'edge-cache',    name: 'edge-cache',    owner: 'Jun Tanaka',    region: 'global',    status: 'Live',    deploys: 318,    uptime: 100,    latency: 21,    cost: 1430,    updated: '2026-10-31',  },  {    id: 'auth',    name: 'auth',    owner: 'Mara Lindqvist',    region: 'eu-west',    status: 'Live',    deploys: 176,    uptime: 99.99,    latency: 66,    cost: 540,    updated: '2026-10-28',  },  {    id: 'ingest',    name: 'ingest',    owner: 'Lena Fischer',    region: 'us-east',    status: 'Live',    deploys: 264,    uptime: 99.93,    latency: 131,    cost: 2120,    updated: '2026-10-30',  },]const min = 60_000const BOX =  'display:grid;align-content:center;' +  'gap:2px;' +  'padding:var(--veneer-space-related);' +  'border:var(--veneer-border-hairline) ' +  'solid var(--veneer-color-border);' +  'border-radius:' +  'var(--veneer-radius-surface);' +  'background:var(--veneer-color-surface);' +  'color:var(--veneer-color-text);' +  'font-size:var(--veneer-text-caption-size);' +  'min-inline-size:0'const projectOf = (c: unknown) => {  const n = Number(typeof c === 'function' ? c() : c)  return Number.isInteger(n) && n > 0    ? PROJECTS[(n - 1) % PROJECTS.length]    : undefined}function Box({  tall,  children,  ...rest}: {  tall?: number  children?: unknown  [attr: string]: unknown}) {  return (    <div      style={        tall ? `${BOX};min-block-size:${tall}px` : BOX      }      {...rest}    >      {projectOf(children) ? (        <>          <strong>{projectOf(children)!.name}</strong>          <span            style={              'color:var(--veneer-color-text-subtle)'            }          >            {projectOf(children)!.region}          </span>        </>      ) : (        children      )}    </div>  )}const FILL =  'block-size:280px;' +  'border:var(--veneer-border-hairline) ' +  'solid var(--veneer-color-border);' +  'border-radius:' +  'var(--veneer-radius-surface);' +  'padding-inline:var(--veneer-space-group)'

A sign-in page

Previews pin the screen's height with --veneer-viewport-block. In an app, leave that out.

tsx
import { state } from '@kerf-js/core'import {  Button,  Center,  Cover,  Heading,  Input,  Row,  Stack,  Text} from '@kerf-js/veneer'export function SignIn() {  const sent = state(false)  const send = (e: Event) => {    e.preventDefault()    sent.value = true  }  return (    <Cover      style={`${SCREEN};--veneer-viewport-block:520px`}      gap="group"    >      <Row        justify="between"        gap="tight"      >        <Text role="heading">kerf</Text>        <Button          emphasis="ghost"          tone="neutral"          size="sm"        >          Help        </Button>      </Row>      <Center        data-part="center"        max="narrow"      >        {sent.value ? (          <Stack            gap="related"            align="center"          >            <Heading level={3}>Check your email</Heading>            <Text subtle>              We sent a sign-in link to you.            </Text>            <Button              emphasis="ghost"              tone="neutral"              onClick={() => (sent.value = false)}            >              Use another address            </Button>          </Stack>        ) : (          signInForm(send)        )}      </Center>      <Text        role="caption"        subtle      >        © 2026 kerf      </Text>    </Cover>  )}const SCREEN =  '--veneer-viewport-block:360px;' +  'border:var(--veneer-border-hairline) ' +  'solid var(--veneer-color-border);' +  'border-radius:' +  'var(--veneer-radius-surface);' +  'padding-inline:var(--veneer-space-group)'function signInForm(send: (e: Event) => void) {  return (    <Stack      as="form"      gap="group"      onSubmit={send}    >      <Stack gap="hair">        <Heading level={3}>Sign in to kerf</Heading>        <Text subtle>          We'll email you a link. No password needed.        </Text>      </Stack>      <Input        label="Work email"        type="email"        required      />      <Button        type="submit"        full      >        Email me a link      </Button>    </Stack>  )}

A not found page

Use Cover to fill the screen and Center to put a message in the middle.

tsx
import {  Button,  Center,  Cover,  Heading,  Stack,  Text} from '@kerf-js/veneer'export function NotFound() {  return (  <Cover style={SCREEN}>    <Center      data-part="center"      max="narrow"      intrinsic    >      <Stack        gap="related"        align="center"      >        <Text          role="eyebrow"          subtle        >          404        </Text>        <Heading          level={3}          role="title"        >          No such project        </Heading>        <Text subtle>          It may have been renamed, or you may not have          access.        </Text>        <Button          icon="arrow-left"          emphasis="outline"          tone="neutral"        >          Back to projects        </Button>      </Stack>    </Center>  </Cover>  )}const SCREEN =  '--veneer-viewport-block:360px;' +  'border:var(--veneer-border-hairline) ' +  'solid var(--veneer-color-border);' +  'border-radius:' +  'var(--veneer-radius-surface);' +  'padding-inline:var(--veneer-space-group)'

When to use

Use it for

  • Sign-in and sign-up pages
  • Error and empty pages
  • A landing hero

Reach for something else

  • Center, when it only needs to centre across, not down
  • Shell, when the page has navigation

Accessibility

  • It sets no landmark. Pass as="main" when it is the page.

API Reference

PropTypeDefaultDescription
childrenunknown—What it contains.
as"div" | "section" | "article" | "main" | "header" | "footer" | "nav" | "…—The element it renders as.
gap"section" | "none" | "hair" | "tight" | "related" | "group" | "page"—The least space kept between the parts, from the space scale.
min"screen" | "fill"—screen fills the viewport, fill fills its parent.

RelatedCenterShellSign in