Tablet Frame

A device that floats over a game or a full-bleed canvas. A 16:10 screen centered over everything, that closes without losing its state and lowers out of the way to show what is behind it.

21:42

Unit 12

On patrol, Midtown

  • Dispatch
  • Map
  • Records
  • Messages
  • Evidence
  • Settings

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add tablet-frame

Usage#

usage.tsxTSX
import { TabletFrame, TabletFrameScreen } from "@/components/ui/tablet-frame"export function Example({ open }: { open: boolean }) {  return (    <TabletFrame open={open}>      <TabletFrameScreen>        <Layout className="h-full">…</Layout>      </TabletFrameScreen>    </TabletFrame>  )}

Anatomy#

import { TabletFrame, TabletFrameScreen } from "@/components/ui/tablet-frame"<TabletFrame>  <TabletFrameScreen /></TabletFrame>

Open and closed#

open (true by default) shows the device. Closing it fades it out with a small drop, but never unmounts it: the tree stays mounted and laid out, so a form keeps what was typed, a map keeps its size and a tab keeps its scroll. While closed it is inert, so focus and the accessibility tree leave with the picture. Type a note, close the tablet and open it again.

21:42

Lowered#

lowered drops the device by 80% of its own height, so its top edge stays in view as a handle, the way a player lowers a tablet to look at the street. Wire it to a held key or button: it eases down while held and back up on release. The drop is a share of the screen, not of the window, so it reads the same in any container.

21:42

Unit 12

On patrol, Midtown

  • Dispatch
  • Map
  • Records
  • Messages
  • Evidence
  • Settings

Scaling#

The screen is 112.5rem × 70.3125rem, 1800 by 1125 at 16px, and below that it narrows with the window to keep a 1rem margin. To fill a share of any display, scale the root font size: the device, its text and its spacing grow and shrink together, and every popover, menu and tooltip stays on its trigger, which a CSS zoom on the device would not do.

use-tablet-scale.tsTSX
import * as React from "react"/** Fit the device to 88% of the window by scaling the rem. */export function useTabletScale(fill = 0.88) {  React.useLayoutEffect(() => {    const root = document.documentElement    const fit = () => {      const scale = Math.min((innerWidth * fill) / 1800, (innerHeight * fill) / 1125)      root.style.fontSize = `${16 * scale}px`    }    fit()    addEventListener("resize", fit)    return () => {      removeEventListener("resize", fit)      root.style.fontSize = ""    }  }, [fill])}

Inside a container#

The frame is fixed over the window. To preview it inside a box (a docs page, a storybook, a marketing shot), give the box a transform: it becomes the containing block, and the frame centers in it. Size the screen to the box with className; it keeps its 16:10 ratio. Every example on this page is built this way.

<div className="relative aspect-[16/10] w-full overflow-hidden transform-gpu">  <TabletFrame>    <TabletFrameScreen className="w-[88%]">…</TabletFrameScreen>  </TabletFrame></div>

API reference#

TabletFrame

The layer: fixed inset-0, transparent to the pointer, centering the screen. Closed, it is inert. Every div prop is forwarded; className is merged last.

PropTypeDefaultDescription
openbooleantrueShows the device. Closed, it fades with a small drop and stays mounted, so its tree keeps its state.
loweredbooleanfalseDrops the device by 80% of its own height, so its top edge stays in view as a handle.
classNamestring-Merged last onto the layer.
Data attributeDescription
data-slot"tablet-frame".
data-state"open" or "closed".
data-opentrue or false, mirroring open.
data-loweredPresent while lowered.

TabletFrameScreen

The device: a 16:10 column that clips its content to rounded-2xl, on the app canvas (bg-canvas, declared as --surface). A docked Layout fits it concentrically. Every div prop is forwarded.

PropTypeDefaultDescription
classNamestring-Merged last, so a w-* resizes the device at the same ratio.
Data attributeDescription
data-slot"tablet-frame-screen".
CSS variableDescription
--surfaceSet to var(--canvas), the floor a docked Layout sits on.

FAQ#

Yes. The frame's layer lets the pointer through; only the screen takes clicks. When it is lowered, everything above its top edge belongs to the game again.