Complete page · Original
Vellum landing
A complete product marketing page: working nav and mobile menu, typographic hero, interactive Monday sheet, pricing period toggle, request dialog, FAQ and footer.
Live preview
The whole page
Runnable project
v0.4.1Usage & source
"use client";
import type { CSSProperties } from "react";
import { VellumLanding } from "./pages/vellum-landing/VellumLanding";
import "./components/styles.css";
import "./pages/vellum-landing/vellum-landing.css";
const themeStyle = {"--stepkit-bg":"#f7f7f8","--stepkit-surface":"#ffffff","--stepkit-surface-muted":"#f0f0f3","--stepkit-fg":"#17181c","--stepkit-muted":"#626572","--stepkit-border":"#e3e4e9","--stepkit-border-strong":"#c8cad3","--stepkit-code":"#f1f1f4","--stepkit-shadow":"0 20px 60px rgba(31, 26, 64, .10)","--stepkit-accent":"#2563eb","--stepkit-accent-strong":"#1d4ed8","--stepkit-accent-soft":"#e8efff","--stepkit-accent-on-soft":"#1d4ed8","--stepkit-accent-text":"#ffffff","--stepkit-error":"#b43145","--stepkit-placeholder":"#626572","--stepkit-radius":"16px","--stepkit-space":"16px","--stepkit-control-height":"44px","--stepkit-font-sans":"Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif"} as CSSProperties;
export default function App() {
return (
<div className="stepkit-theme" data-stepkit-preset="modern" style={themeStyle}>
<VellumLanding />
</div>
);
}Dependencies
[email protected] [email protected]
/*
* Stepkit 0.4.1
* Copyright (c) 2026 Vladislav Stepanov
*
* Original Stepkit portions in this release are licensed under the MIT License. Adapted portions identify their upstream licence and attribution in the copied source and in the entry-specific upstream notice files.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
"use client";
import { useEffect, useId, useRef, useState, type FormEvent, type KeyboardEvent as ReactKeyboardEvent } from "react";
import { Badge } from "../../components/Badge";
import { Button } from "../../components/Button";
import { Dialog } from "../../components/Dialog";
import { FAQ } from "../../components/FAQ";
import { Footer } from "../../components/Footer";
import { Switch } from "../../components/Switch";
import { Toast } from "../../components/Toast";
import "./vellum-landing.css";
type SignalKind = "risk" | "decision" | "win";
interface SheetSignal {
id: string;
day: string;
kind: SignalKind;
title: string;
owner: string;
}
interface Plan {
id: string;
name: string;
monthly: number;
yearly: number;
description: string;
features: string[];
cta: string;
recommended?: boolean;
}
const WEEK = [
{ id: "mon", label: "Mon", date: "16" },
{ id: "tue", label: "Tue", date: "17" },
{ id: "wed", label: "Wed", date: "18" },
{ id: "thu", label: "Thu", date: "19" },
{ id: "fri", label: "Fri", date: "20" }
] as const;
const DEMO_SIGNALS: SheetSignal[] = [
{ id: "s1", day: "mon", kind: "risk", title: "Checkout retry storm after the card-network blip", owner: "Maya Chen" },
{ id: "s2", day: "mon", kind: "decision", title: "Maya owns the Monday sheet through April", owner: "Maya Chen" },
{ id: "s3", day: "tue", kind: "decision", title: "Freeze pricing copy until the studio review", owner: "Jonah Adeyemi" },
{ id: "s4", day: "tue", kind: "win", title: "Activation mail recovered without a vendor ticket", owner: "Priya Shah" },
{ id: "s5", day: "wed", kind: "risk", title: "EU data map still missing the digest store", owner: "Maya Chen" },
{ id: "s6", day: "thu", kind: "decision", title: "Cut the unused weekly digest, keep the sheet", owner: "Jonah Adeyemi" },
{ id: "s7", day: "fri", kind: "win", title: "Design ran Monday from the sheet, not Slack", owner: "Priya Shah" },
{ id: "s8", day: "fri", kind: "risk", title: "Vendor token rotation is still on a calendar reminder", owner: "Sam Okada" }
];
const PLANS: Plan[] = [
{
id: "sheet",
name: "Single sheet",
monthly: 0,
yearly: 0,
description: "One team, one Monday picture, source included.",
features: ["Local demo workspace", "Up to 40 signals", "CSV export of the sheet"],
cta: "Use the free sheet"
},
{
id: "studio",
name: "Studio",
monthly: 29,
yearly: 24,
description: "The weekly rhythm for a product trio.",
features: ["Shared studio board", "Brief history", "Theme tokens that travel"],
cta: "Choose Studio",
recommended: true
},
{
id: "floor",
name: "Floor",
monthly: 79,
yearly: 64,
description: "Several sheets, one operating floor.",
features: ["Multiple briefs", "Role permissions", "SSO when you wire identity"],
cta: "Choose Floor"
}
];
const FAQ_ITEMS = [
{
id: "who",
question: "Who is the Monday sheet for?",
answer: "Product trios who currently reconstruct the week from chat. Vellum is the picture you open before stand-up — not another channel to check."
},
{
id: "signals",
question: "What counts as a signal?",
answer: "A risk, a decision, or a win, with an owner and a day. If it would otherwise live in Slack until Friday, it belongs on the sheet."
},
{
id: "brief",
question: "How does a signal become the brief?",
answer: "Days cluster the sheet. Open risks stay loud. Closed wins stay visible so Monday is a record, not a transcript of who talked the longest."
},
{
id: "demo",
question: "Can I start a real studio from this page?",
answer: "You can run the sheet in the browser and choose a sample plan. Billing, accounts, and a waitlist need your own backend before this is a live product."
}
];
const NAV = [
{ href: "#product", label: "Product" },
{ href: "#workflow", label: "How it works" },
{ href: "#pricing", label: "Pricing" },
{ href: "#faq", label: "FAQ" }
];
function countFor(day: string) {
return DEMO_SIGNALS.filter((signal) => signal.day === day).length;
}
function priceLabel(plan: Plan, yearly: boolean) {
const value = yearly ? plan.yearly : plan.monthly;
if (value === 0) return "$0";
return `$${value}`;
}
function kindTone(kind: SignalKind) {
if (kind === "risk") return "danger" as const;
if (kind === "win") return "success" as const;
return "accent" as const;
}
export function VellumLanding() {
const menuId = useId();
const planTitleId = useId();
const [menuOpen, setMenuOpen] = useState(false);
const [yearly, setYearly] = useState(true);
const [activeDay, setActiveDay] = useState<(typeof WEEK)[number]["id"]>("wed");
const [requestOpen, setRequestOpen] = useState(false);
const [selectedPlan, setSelectedPlan] = useState<Plan | null>(null);
const [toast, setToast] = useState("");
const [requestError, setRequestError] = useState("");
const menuButtonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<HTMLDivElement>(null);
const planDialogRef = useRef<HTMLDialogElement>(null);
const daySignals = DEMO_SIGNALS.filter((signal) => signal.day === activeDay);
useEffect(() => {
if (!menuOpen) return;
const onKey = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setMenuOpen(false);
menuButtonRef.current?.focus();
}
};
document.addEventListener("keydown", onKey);
const first = menuRef.current?.querySelector<HTMLElement>("a, button");
first?.focus();
return () => document.removeEventListener("keydown", onKey);
}, [menuOpen]);
useEffect(() => {
const node = planDialogRef.current;
if (!node) return;
if (selectedPlan && !node.open) node.showModal();
if (!selectedPlan && node.open) node.close();
}, [selectedPlan]);
const closeMenu = () => {
setMenuOpen(false);
menuButtonRef.current?.focus();
};
const submitRequest = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
const team = String(data.get("team") || "").trim();
const email = String(data.get("email") || "").trim();
if (!team || !email.includes("@")) {
setRequestError("Add a team name and a real email so the demo can validate the form.");
return;
}
setRequestError("");
setRequestOpen(false);
setToast("Demo only — connect this form to your waitlist or CRM before you ship it.");
};
return (
<div className="vellum-landing" data-vellum-page="landing">
<a className="vellum-landing__skip" href="#product">Skip to product</a>
<header className="vellum-landing__header">
<a className="vellum-landing__brand" href="#top" id="top">
<span className="vellum-landing__mark" aria-hidden="true" />
Vellum
</a>
<nav className="vellum-landing__nav" aria-label="Product">
{NAV.map((item) => (
<a className="vellum-landing__nav-link" href={item.href} key={item.href}>{item.label}</a>
))}
</nav>
<div className="vellum-landing__header-actions" id="request">
<Button className="vellum-landing__ghost-link" label="See the sheet" variant="ghost" onClick={() => document.getElementById("product")?.scrollIntoView({ behavior: "smooth", block: "start" })} />
<Dialog
className="vellum-landing__request"
triggerText="Request a brief"
title="Request a Monday brief"
body="The form stays on this page. Connect it to a waitlist before you ship."
open={requestOpen}
onOpenChange={(open) => {
setRequestOpen(open);
if (!open) setRequestError("");
}}
>
<form className="vellum-landing__form" onSubmit={submitRequest} noValidate>
<label>
Team name
<input name="team" type="text" autoComplete="organization" required />
</label>
<label>
Work email
<input name="email" type="email" autoComplete="email" required />
</label>
{requestError ? <p className="vellum-landing__error" role="alert">{requestError}</p> : null}
<Button type="submit" label="Stage the request" />
</form>
</Dialog>
<button
ref={menuButtonRef}
className="vellum-landing__menu-button"
type="button"
aria-expanded={menuOpen}
aria-controls={menuId}
aria-haspopup="true"
onClick={() => setMenuOpen((open) => !open)}
>
{menuOpen ? "Close" : "Menu"}
</button>
</div>
</header>
{menuOpen ? (
<div className="vellum-landing__menu" id={menuId} ref={menuRef} role="dialog" aria-label="Menu">
{NAV.map((item) => (
<a href={item.href} key={item.href} onClick={closeMenu}>{item.label}</a>
))}
<Button label="See the sheet" variant="outline" onClick={() => { closeMenu(); document.getElementById("product")?.scrollIntoView({ behavior: "smooth", block: "start" }); }} />
<Button label="Request a brief" onClick={() => { setMenuOpen(false); setRequestOpen(true); }} />
</div>
) : null}
<div className="vellum-landing__body" {...(menuOpen ? { inert: true } : {})}>
<section className="vellum-landing__hero" aria-labelledby="vellum-hero-title">
<div className="vellum-landing__hero-copy">
<p className="vellum-landing__kicker">Weekly operating picture</p>
<h1 id="vellum-hero-title">The week, on one sheet of vellum.</h1>
<p className="vellum-landing__lede">
Vellum is a Monday brief for product teams who are tired of reconstructing the week from chat. Log the risk, the decision, the win. The sheet is the product.
</p>
<div className="vellum-landing__hero-actions">
<Button label="Request a brief" onClick={() => setRequestOpen(true)} />
<Button label="Inspect the sheet" variant="outline" onClick={() => document.getElementById("product")?.scrollIntoView({ behavior: "smooth", block: "start" })} />
</div>
<p className="vellum-landing__demo-note">Demo · changes reset on refresh</p>
</div>
<div className="vellum-landing__hero-visual" aria-hidden="false">
<WeekSheet activeDay={activeDay} onSelectDay={setActiveDay} signals={daySignals} compact />
</div>
</section>
<section className="vellum-landing__product" id="product" aria-labelledby="vellum-product-title">
<div className="vellum-landing__product-intro">
<h2 id="vellum-product-title">A sheet you can actually run Monday from.</h2>
<p>Sample week of 16 March. Choose a day to read the signals that landed.</p>
</div>
<WeekSheet activeDay={activeDay} onSelectDay={setActiveDay} signals={daySignals} />
</section>
<section className="vellum-landing__workflow" id="workflow" aria-labelledby="vellum-workflow-title">
<div className="vellum-landing__workflow-intro">
<h2 id="vellum-workflow-title">Capture. Cluster. Brief.</h2>
<p>Three moves, one page. Not a feature grid.</p>
</div>
<ol className="vellum-landing__steps">
<li>
<span>01</span>
<h3>Write the signal while it is still true</h3>
<p>A risk, a decision, or a win. Owner and day are enough. Capture it before the week turns it into folklore.</p>
</li>
<li>
<span>02</span>
<h3>Let the week arrange itself</h3>
<p>The sheet clusters by day. Open risks stay loud. Closed wins stay on the page so the team can see what actually moved.</p>
</li>
<li>
<span>03</span>
<h3>Read the brief, then leave</h3>
<p>Monday is a picture, not a stand-up transcript. Open items stay on the sheet so the room does not relitigate them.</p>
</li>
</ol>
</section>
<section className="vellum-landing__pricing" id="pricing" aria-labelledby="vellum-pricing-title">
<div className="vellum-landing__pricing-head">
<div>
<h2 id="vellum-pricing-title">Sample prices, real toggle.</h2>
<p>Figures are demonstration amounts. Checkout needs your billing provider; choosing a plan only records the selection here.</p>
</div>
<Switch label="Bill yearly" description={yearly ? "Amounts shown per month, billed annually." : "Amounts shown per month."} checked={yearly} onCheckedChange={setYearly} />
</div>
<div className="vellum-landing__plans">
{PLANS.map((plan) => (
<article className="vellum-landing__plan" data-recommended={plan.recommended || undefined} key={plan.id}>
{plan.recommended ? <Badge tone="accent">Usual studio choice</Badge> : <Badge tone="neutral">Sample plan</Badge>}
<h3>{plan.name}</h3>
<p className="vellum-landing__plan-copy">{plan.description}</p>
<p className="vellum-landing__price">
<strong>{priceLabel(plan, yearly)}</strong>
<span>{plan.monthly === 0 ? "to evaluate the sheet" : yearly ? "per month, billed annually" : "per month"}</span>
</p>
<ul>
{plan.features.map((feature) => <li key={feature}>{feature}</li>)}
</ul>
<Button
label={plan.cta}
variant={plan.recommended ? "solid" : "outline"}
onClick={() => setSelectedPlan(plan)}
/>
</article>
))}
</div>
<p className="vellum-landing__plan-status" aria-live="polite">
{selectedPlan ? `Selected in this demo: ${selectedPlan.name}.` : "No plan selected yet."}
</p>
</section>
<section className="vellum-landing__faq" id="faq">
<FAQ
title="Questions from the floor"
intro="The weekly sheet is a short operating picture. These are the questions teams ask before they give Monday to it."
items={FAQ_ITEMS}
/>
</section>
<section className="vellum-landing__license" id="license">
<h2>Licence</h2>
<p>Original Vellum page source is MIT, same as Stepkit. Keep NOTICE.md with the copied files. Adapted Stepkit components in a mixed project keep their upstream notices.</p>
</section>
<Footer
brand="Vellum"
note="A Monday sheet for product teams. Sample content, local interactions, no invented customers."
groups={[
{ title: "Product", links: [{ label: "The sheet", href: "#product" }, { label: "How it works", href: "#workflow" }] },
{ title: "Evaluate", links: [{ label: "Pricing", href: "#pricing" }, { label: "FAQ", href: "#faq" }] },
{ title: "Source", links: [{ label: "Licence", href: "#license" }, { label: "Request a brief", href: "#request" }] }
]}
copyright="Sample product page · Stepkit 0.4.0 · MIT"
/>
</div>
<dialog
ref={planDialogRef}
className="vellum-landing__plan-dialog"
aria-labelledby={planTitleId}
onClose={() => setSelectedPlan(null)}
onCancel={(event) => {
event.preventDefault();
setSelectedPlan(null);
}}
>
{selectedPlan ? (
<>
<h2 id={planTitleId}>{selectedPlan.name}</h2>
<p>
{priceLabel(selectedPlan, yearly)} {selectedPlan.monthly === 0 ? "to evaluate." : yearly ? "per month, billed annually." : "per month."} This dialog is the integration seam: send the selected plan id to your billing backend, then redirect. Nothing is charged from the template.
</p>
<div className="vellum-landing__dialog-actions">
<Button label="Keep this plan in the demo" onClick={() => { setToast(`${selectedPlan.name} kept in local demo state.`); setSelectedPlan(null); }} />
<Button label="Close" variant="outline" onClick={() => setSelectedPlan(null)} />
</div>
</>
) : null}
</dialog>
{toast ? <Toast title="Local demo" message={toast} onOpenChange={(open) => { if (!open) setToast(""); }} /> : null}
</div>
);
}
function WeekSheet({
activeDay,
onSelectDay,
signals,
compact = false
}: {
activeDay: string;
onSelectDay: (day: (typeof WEEK)[number]["id"]) => void;
signals: SheetSignal[];
compact?: boolean;
}) {
return (
<figure className="vellum-sheet" data-compact={compact}>
<figcaption>
<span>Week of 16 March</span>
<span>Demo data · {DEMO_SIGNALS.length} signals</span>
</figcaption>
<div
className="vellum-sheet__days"
role="group"
aria-label="Days in the sample week"
onKeyDown={(event: ReactKeyboardEvent<HTMLDivElement>) => {
const ids = WEEK.map((day) => day.id);
const index = ids.indexOf(activeDay as (typeof WEEK)[number]["id"]);
let next = index;
if (event.key === "ArrowRight") next = (index + 1) % ids.length;
else if (event.key === "ArrowLeft") next = (index - 1 + ids.length) % ids.length;
else if (event.key === "Home") next = 0;
else if (event.key === "End") next = ids.length - 1;
else return;
event.preventDefault();
onSelectDay(ids[next]);
event.currentTarget.querySelectorAll("button")[next]?.focus();
}}
>
{WEEK.map((day) => (
<button
key={day.id}
type="button"
aria-pressed={day.id === activeDay}
onClick={() => onSelectDay(day.id)}
>
<span>{day.label}</span>
<strong>{day.date}</strong>
<em>{countFor(day.id)} {countFor(day.id) === 1 ? "signal" : "signals"}</em>
</button>
))}
</div>
<ul className="vellum-sheet__list">
{signals.map((signal) => (
<li key={signal.id}>
<Badge tone={kindTone(signal.kind)} size="sm">{signal.kind}</Badge>
<div>
<strong>{signal.title}</strong>
<span>{signal.owner}</span>
</div>
</li>
))}
</ul>
</figure>
);
}
export default VellumLanding;
pages/vellum-landing/VellumLanding.tsxpages/vellum-landing/vellum-landing.csscomponents/Badge.tsxcomponents/Button.tsxcomponents/Dialog.tsxcomponents/FAQ.tsxcomponents/Accordion.tsxcomponents/Footer.tsxcomponents/Switch.tsxcomponents/Toast.tsxcomponents/styles.cssDownload and run
This is a complete React + Vite page. You do not need a pre-existing shadcn setup. Individual Stepkit components remain available from the registry if you only want one control.
unzip vellum-landing-0.4.1.zip && npm ci && npm run devIntegration prompt
Unzip vellum-landing-0.4.1.zip into a new folder.
npm ci
npm run dev
This is a complete React + Vite page. You do not need a pre-existing shadcn setup. Individual Stepkit components remain available from the registry if you only want one control.
Keep NOTICE.md with the copied source. Change colours by editing the --stepkit-* tokens on the .stepkit-theme wrapper in src/App.tsx.
Forms, plan choice, search, and edits are local demos until you connect your backend.
Usage:
```tsx
"use client";
import type { CSSProperties } from "react";
import { VellumLanding } from "./pages/vellum-landing/VellumLanding";
import "./components/styles.css";
import "./pages/vellum-landing/vellum-landing.css";
const themeStyle = {"--stepkit-bg":"#f7f7f8","--stepkit-surface":"#ffffff","--stepkit-surface-muted":"#f0f0f3","--stepkit-fg":"#17181c","--stepkit-muted":"#626572","--stepkit-border":"#e3e4e9","--stepkit-border-strong":"#c8cad3","--stepkit-code":"#f1f1f4","--stepkit-shadow":"0 20px 60px rgba(31, 26, 64, .10)","--stepkit-accent":"#6950df","--stepkit-accent-strong":"#5137c7","--stepkit-accent-soft":"#eeeafd","--stepkit-accent-on-soft":"#5137c7","--stepkit-accent-text":"#ffffff","--stepkit-error":"#b43145","--stepkit-placeholder":"#626572","--stepkit-radius":"16px","--stepkit-space":"16px","--stepkit-control-height":"44px","--stepkit-font-sans":"Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif"} as CSSProperties;
export default function App() {
return (
<div className="stepkit-theme" data-stepkit-preset="modern" style={themeStyle}>
<VellumLanding />
</div>
);
}
```Accessibility & limits
- Primary navigation uses in-page destinations and a disclosure menu on small screens.
- The week sheet exposes selected day state.
- Dialogs restore focus and close with Escape.
Limitations
- The request form and plan choice stay in the browser until you connect billing or a waitlist.
- The workspace screen is a separate template ZIP.
Provenance
Original source under the MIT licence.
Original Stepkit implementation.
Required notices: NOTICE.md, PROVENANCE-stepkit-vellum-landing.md