creator docs · reference · generated
Keepsake — the thing they print, share or hand out
This is the same document the Vibes code generator reads when it
builds apps — rendered from
prompts/pkg/llms/keepsake.md on every deploy, so it
can't drift from what the platform actually does. Read it from your terminal any
time with npx vibes-diy skills.
The first frame is the keepsake and the fields that edit it, together.
Fields beside it on a wide screen, below it on a phone, each keystroke
redrawing it: the example's studio grid.
A keepsake is drawn with the reskin technique: one data-reskin wrapper re-scoping the look's
tokens and one SKIN string rendered as its first child. The reskin doc loads with this one.
Tickets, invitations, menus, posters, certificates, place cards, a recipe
card: the designed object is the point, and a page of Cards and RowCards
around it makes it look like an admin screen. Build one framed artifact and
let the components edit it. An app that keeps several (an invitation per
party) lists them below the studio; picking one opens it there.
- The keepsake is plain elements styled by the skin. One
.keepsakeelement with a fixed shape (aspect-ratio: 5 / 7for a card,2 / 1for a ticket,1 / 1.414for an A-size poster) holds the words asdivs andspans with the app's own classes (.keepsake-title,.keepsake-meta), set in the display face, with the border, ornament, paper and stamp drawn in theSKINstring. It is never aCard, and its words are neverHeadings: the hook classes belong to the screen, the keepsake has its own. - The components are the editing controls around it.
FieldandInputfor the words,Segmentedfor a variant (theme, size),Buttonfor Print and Share. The keepsake opens filled with a believable example. - Print shows only the keepsake. A
@media printblock in the skin hides everything else and sets@pageto the keepsake's size; the Print button callswindow.print(). - Its colours come from the wrapper's tokens, so the Look tab and a later reskin restyle the keepsake along with the controls.
jsximport React, { useState } from "react";
import { Button, Field, Input, Stack } from "@vibes.diy/ui";
const SKIN = `
@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,wght@0,600;1,400&display=swap');
[data-reskin] .studio { display: grid; gap: 24px; padding: 16px; grid-template-columns: 1fr; }
@media (min-width: 48rem) { [data-reskin] .studio { grid-template-columns: 22rem 1fr; align-items: start; } [data-reskin] .studio > .keepsake { order: 2; } }
[data-reskin] .keepsake { aspect-ratio: 5 / 7; width: min(100%, 26rem); margin-inline: auto; padding: 12%; display: grid; place-content: center; gap: 14px;
text-align: center; background: var(--surface); color: var(--ink); outline: 1px solid var(--ink); outline-offset: -14px;
box-shadow: 0 18px 40px color-mix(in srgb, var(--ink) 18%, transparent); font-family: 'Fraunces', serif; }
[data-reskin] .keepsake-eyebrow { font-style: italic; color: var(--text-secondary); }
[data-reskin] .keepsake-title { font-size: clamp(28px, 7vw, 44px); font-weight: 600; line-height: 1.05; }
[data-reskin] .keepsake-rule { width: 40%; height: 1px; margin-inline: auto; background: var(--purple); }
@media print {
@page { size: 5in 7in; margin: 0; }
body:has([data-reskin]) * { visibility: hidden; }
[data-reskin] .keepsake, [data-reskin] .keepsake * { visibility: visible; }
[data-reskin] .keepsake { position: fixed; inset: 0; width: 100%; box-shadow: none; }
}
`;
export default function App() {
const [title, setTitle] = useState("Maya turns thirty");
const [when, setWhen] = useState("Saturday 14 June · 7pm · The Rooftop");
return (
<div data-reskin>
<style>{SKIN}</style>
<div className="studio">
<div className="keepsake">
<span className="keepsake-eyebrow">You are invited</span>
<span className="keepsake-title">{title}</span>
<span className="keepsake-rule" />
<span>{when}</span>
</div>
<Stack gap={3}>
<Field label="Headline">{(id, d) => <Input id={id} describedBy={d} value={title} onChange={setTitle} />}</Field>
<Field label="When and where">{(id, d) => <Input id={id} describedBy={d} value={when} onChange={setWhen} />}</Field>
<Button onClick={() => window.print()}>Print the invitation</Button>
</Stack>
</div>
</div>
);
}