Ledger: Mac app
A five-page site for a Mac app: an interactive desktop hero, a menu-bar tour, a native-features bento, a device lineup, license-or-subscription pricing, a download page, release notes and support.
// Ballmac UI: Ledger template fonts. https://ui.ballmac.com/templates/template-ledger
import { Inter, Newsreader } from "next/font/google"
/** The italic serif used for one emphasised word per heading. Body text uses the system font, like a Mac app. */
export const ledgerSerif = Newsreader({ variable: "--ledger-serif", subsets: ["latin"], style: ["normal", "italic"], display: "swap", preload: false })
/** Used where San Francisco is not installed (Windows, Linux, Android), so the page looks the same everywhere. */
export const ledgerSans = Inter({ variable: "--ledger-sans", subsets: ["latin"], display: "swap", preload: false })// Ballmac UI: Ledger template shell. https://ui.ballmac.com/templates/template-ledger
"use client"
import * as React from "react"
import { Menu, X } from "lucide-react"
import { AppIcon } from "@/components/ballmac/mac-icons"
import { ledgerSans, ledgerSerif } from "@/components/ballmac/templates/ledger/ledger-fonts"
import { cn } from "@/lib/utils"
type LedgerPage = "home" | "download" | "pricing" | "changelog" | "support"
type LedgerHrefs = Record<LedgerPage, string>
const defaultHrefs: LedgerHrefs = { home: "/ledger", download: "/ledger/download", pricing: "/ledger/pricing", changelog: "/ledger/changelog", support: "/ledger/support" }
/** Ledger's palette: Apple-calm. Cool white, soft grey surfaces, one blue. Dark mode follows the system. */
const ledgerCss = `
.ledger-theme{--background:oklch(0.99 0.002 250);--foreground:oklch(0.2 0.01 255);--card:oklch(1 0 0);--card-foreground:oklch(0.2 0.01 255);--popover:oklch(1 0 0);--popover-foreground:oklch(0.2 0.01 255);--primary:oklch(0.2 0.01 255);--primary-foreground:oklch(0.99 0.002 250);--secondary:oklch(0.965 0.004 250);--secondary-foreground:oklch(0.2 0.01 255);--muted:oklch(0.965 0.004 250);--muted-foreground:oklch(0.5 0.012 255);--accent:oklch(0.955 0.006 250);--accent-foreground:oklch(0.2 0.01 255);--border:oklch(0.2 0.01 255 / 10%);--input:oklch(0.2 0.01 255 / 14%);--ring:oklch(0.6 0.19 255);--surface:oklch(0.975 0.003 250);--chart-1:oklch(0.52 0.2 255);--ledger-on-accent:oklch(1 0 0);--chart-2:oklch(0.48 0.14 155);--chart-3:oklch(0.78 0.16 75);--chart-4:oklch(0.62 0.2 305);--chart-5:oklch(0.68 0.19 25);--radius:1rem}
.dark .ledger-theme{--background:oklch(0.16 0.006 255);--foreground:oklch(0.96 0.004 250);--card:oklch(0.2 0.008 255);--card-foreground:oklch(0.96 0.004 250);--popover:oklch(0.22 0.008 255);--popover-foreground:oklch(0.96 0.004 250);--primary:oklch(0.96 0.004 250);--primary-foreground:oklch(0.2 0.01 255);--secondary:oklch(0.25 0.009 255);--secondary-foreground:oklch(0.96 0.004 250);--muted:oklch(0.24 0.009 255);--muted-foreground:oklch(0.72 0.012 250);--accent:oklch(0.27 0.01 255);--accent-foreground:oklch(0.96 0.004 250);--border:oklch(1 0 0 / 11%);--input:oklch(1 0 0 / 15%);--ring:oklch(0.7 0.17 255);--surface:oklch(0.18 0.007 255);--chart-1:oklch(0.72 0.15 255);--ledger-on-accent:oklch(0.16 0.01 255);--chart-2:oklch(0.76 0.16 160);--chart-3:oklch(0.83 0.15 80);--chart-4:oklch(0.72 0.17 305);--chart-5:oklch(0.76 0.16 25)}
`
const links: { key: LedgerPage; label: string }[] = [
{ key: "home", label: "Overview" },
{ key: "pricing", label: "Pricing" },
{ key: "changelog", label: "What’s new" },
{ key: "support", label: "Support" },
]
type LedgerShellProps = React.ComponentProps<"div"> & {
/** The page being shown, so its nav link is marked current. */
page: LedgerPage
/** Override where pages live (used by previews). */
hrefs?: Partial<LedgerHrefs>
}
/** Ledger's frame: a frosted, centred header like a Mac app’s site, and a quiet footer. Wrap every Ledger page in it. */
function LedgerShell({ page, hrefs: overrides, className, style, children, ...props }: LedgerShellProps) {
const hrefs = { ...defaultHrefs, ...overrides }
const [open, setOpen] = React.useState(false)
return (
<div
data-slot="ledger"
className={cn("ledger-theme bg-background text-foreground relative min-h-dvh overflow-x-clip antialiased", ledgerSerif.variable, ledgerSans.variable, className)}
style={{ fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', var(--ledger-sans), ui-sans-serif, system-ui, sans-serif", ...style }}
{...props}
>
<style>{ledgerCss}</style>
<header className="bg-background/72 sticky top-0 z-40 border-b backdrop-blur-xl backdrop-saturate-150">
<div className="mx-auto flex h-12 max-w-6xl items-center justify-between px-4 sm:px-6">
<a href={hrefs.home} className="focus-visible:ring-ring/50 flex items-center gap-2 rounded-md text-sm font-semibold outline-none focus-visible:ring-[3px]">
<AppIcon size={22} tone="blue"><span className="text-[11px] font-bold">L</span></AppIcon> Ledger
</a>
<nav aria-label="Main" className="hidden items-center gap-7 md:flex">
{links.map((l) => (
<a key={l.key} href={hrefs[l.key]} aria-current={page === l.key ? "page" : undefined} className="text-muted-foreground hover:text-foreground aria-[current=page]:text-foreground focus-visible:ring-ring/50 rounded text-[13px] outline-none transition-colors focus-visible:ring-[3px]">
{l.label}
</a>
))}
</nav>
<div className="flex items-center gap-2">
<a href={hrefs.download} className="bg-chart-1 focus-visible:ring-ring/50 inline-flex h-7 items-center rounded-full px-3.5 text-[13px] font-medium text-[var(--ledger-on-accent)] outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]">Download</a>
<button type="button" aria-label={open ? "Close menu" : "Open menu"} aria-expanded={open} aria-controls="ledger-mobile-menu" onClick={() => setOpen((v) => !v)} className="text-muted-foreground focus-visible:ring-ring/50 inline-flex size-8 items-center justify-center rounded-md outline-none focus-visible:ring-[3px] md:hidden">
{open ? <X className="size-5" aria-hidden="true" /> : <Menu className="size-5" aria-hidden="true" />}
</button>
</div>
</div>
{open && (
<nav id="ledger-mobile-menu" aria-label="Mobile" className="border-t px-4 py-2 md:hidden">
{links.map((l) => <a key={l.key} href={hrefs[l.key]} className="hover:bg-accent block rounded-lg px-3 py-2.5 text-sm">{l.label}</a>)}
</nav>
)}
</header>
{children}
<footer className="bg-surface border-t">
<div className="mx-auto max-w-6xl px-4 py-12 sm:px-6">
<div className="grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
{[
{ title: "Ledger", items: [["Overview", "home"], ["What’s new", "changelog"], ["Pricing", "pricing"], ["Download", "download"]] },
{ title: "Support", items: [["Help center", "support"], ["Keyboard shortcuts", "support"], ["Contact us", "support"], ["System status", "support"]] },
{ title: "Company", items: [["About", "home"], ["Press kit", "home"], ["Privacy", "home"], ["Terms", "home"]] },
{ title: "Elsewhere", items: [["Mastodon", "home"], ["GitHub", "home"], ["RSS", "changelog"], ["Newsletter", "home"]] },
].map((c) => (
<div key={c.title}>
<h2 className="text-xs font-semibold">{c.title}</h2>
<ul className="mt-3 space-y-2">
{c.items.map(([label, key]) => <li key={label}><a href={hrefs[key as LedgerPage]} className="text-muted-foreground hover:text-foreground text-xs transition-colors">{label}</a></li>)}
</ul>
</div>
))}
</div>
<p className="text-muted-foreground mt-10 border-t pt-5 text-xs text-pretty">Ledger requires macOS 13 Ventura or later. Apple, Mac, macOS and iCloud are trademarks of Apple Inc. © 2026 Ledger Software, made in Portland.</p>
</div>
</footer>
</div>
)
}
/** A heading with one italic serif word, the template's signature. Wrap the word to emphasise in `<em>`. */
function LedgerHeading({ className, children, as: Tag = "h2", ...props }: React.ComponentProps<"h2"> & { as?: "h1" | "h2" | "h3" }) {
return (
<Tag className={cn("font-semibold tracking-[-0.035em] text-balance [&_em]:font-normal [&_em]:tracking-[-0.02em] [&_em]:[font-family:var(--ledger-serif),ui-serif,Georgia,serif]", className)} {...props}>
{children}
</Tag>
)
}
export { LedgerHeading, LedgerShell, defaultHrefs as ledgerDefaultHrefs, type LedgerHrefs, type LedgerPage, type LedgerShellProps }// Ballmac UI: Ledger home page. https://ui.ballmac.com/templates/template-ledger
"use client"
import * as React from "react"
import { ArrowDownToLine, Cloud, Laptop, Lock, ShieldCheck, Star, Zap } from "lucide-react"
import { Devices1 } from "@/components/ballmac/blocks/devices-1/devices-1"
import { Features7 } from "@/components/ballmac/blocks/features-7/features-7"
import { Pricing4 } from "@/components/ballmac/blocks/pricing-4/pricing-4"
import { Showcase1 } from "@/components/ballmac/blocks/showcase-1/showcase-1"
import { BlurFade } from "@/components/ballmac/blur-fade"
import { Kbd } from "@/components/ballmac/kbd"
import { AppIcon } from "@/components/ballmac/mac-icons"
import { LedgerHeading, LedgerShell, type LedgerHrefs } from "@/components/ballmac/templates/ledger/ledger-theme"
import { cn } from "@/lib/utils"
const press = [
{ outlet: "Mac App Store", quote: "Editor’s Choice" },
{ outlet: "MacStories", quote: "The calmest way to run a business." },
{ outlet: "Six Colors", quote: "A Mac app that feels like one." },
{ outlet: "Daring Fireball", quote: "Fast, plain and correct." },
]
const reviews = [
{ quote: "I closed the month in an afternoon. The keyboard shortcuts alone paid for the license.", name: "Hannah Okafor", role: "Freelance designer", tone: "bg-chart-1/20" },
{ quote: "It’s the first accounting app that didn’t make me feel like I needed an accountant to use it.", name: "Marcus Lindqvist", role: "Founder, Studio Fjord", tone: "bg-chart-2/20" },
{ quote: "Works offline on the train, syncs when I’m home. Exactly how software should behave.", name: "Priya Raman", role: "Independent consultant", tone: "bg-chart-4/20" },
]
const card = "bg-card rounded-3xl border p-7 shadow-[0_1px_2px_0_rgb(0_0_0/0.04)]"
type LedgerHomeProps = React.ComponentProps<"div"> & { hrefs?: Partial<LedgerHrefs> }
/** The Ledger home page: an interactive Mac desktop, a menu-bar tour, a feature bento, devices, reviews and pricing. */
function LedgerHome({ hrefs, ...props }: LedgerHomeProps) {
return (
<LedgerShell page="home" hrefs={hrefs} {...props}>
<main>
<section className="px-4 pt-16 text-center sm:px-6 sm:pt-24">
<BlurFade>
<p className="text-chart-1 text-sm font-semibold">Ledger 2.4 · Now with approvals</p>
<LedgerHeading as="h1" className="mx-auto mt-4 max-w-3xl text-5xl leading-[1.02] sm:text-7xl">
Your books, kept <em>beautifully</em>.
</LedgerHeading>
<p className="text-muted-foreground mx-auto mt-6 max-w-xl text-lg text-pretty sm:text-xl">
Invoices, expenses and reports in a native Mac app that’s fast, private and a pleasure to open on a Monday.
</p>
<div className="mt-9 flex flex-col items-center justify-center gap-3 sm:flex-row sm:gap-5">
<a href={hrefs?.download ?? "/ledger/download"} className="bg-chart-1 focus-visible:ring-ring/50 inline-flex h-12 items-center gap-2 rounded-full px-6 font-medium text-[var(--ledger-on-accent)] outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]">
<ArrowDownToLine className="size-4" aria-hidden="true" /> Download for Mac
</a>
<a href={hrefs?.pricing ?? "/ledger/pricing"} className="text-chart-1 focus-visible:ring-ring/50 rounded-md font-medium underline-offset-4 outline-none hover:underline focus-visible:ring-[3px]">
See pricing ›
</a>
</div>
<p className="text-muted-foreground mt-5 text-xs">Free for 14 days · macOS 13 or later · Apple silicon and Intel</p>
</BlurFade>
<BlurFade className="mx-auto mt-14 max-w-6xl text-start" delay={0.12}>
<Showcase1 app="Ledger" height="38rem" className="shadow-[0_40px_100px_-30px_rgb(0_0_0/0.45)]" />
</BlurFade>
</section>
<section aria-label="Press" className="mx-auto max-w-6xl px-4 py-16 sm:px-6">
<ul className="grid gap-px overflow-hidden rounded-2xl border bg-border sm:grid-cols-2 lg:grid-cols-4">
{press.map((p) => (
<li key={p.outlet} className="bg-card p-6 text-center">
<p className="text-muted-foreground text-xs font-semibold tracking-wide uppercase">{p.outlet}</p>
<p className="mt-2 text-sm font-medium text-pretty [font-family:var(--ledger-serif),ui-serif,Georgia,serif] text-lg italic">“{p.quote}”</p>
</li>
))}
</ul>
</section>
<Features7 title="Always one keystroke away." />
<section className="mx-auto max-w-6xl px-4 py-20 sm:px-6">
<div className="mx-auto max-w-2xl text-center">
<LedgerHeading className="text-4xl sm:text-5xl">A real Mac app, <em>all the way down</em>.</LedgerHeading>
<p className="text-muted-foreground mt-4 text-lg text-pretty">Native, not wrapped. It uses the parts of macOS you already know.</p>
</div>
<div className="mt-12 grid gap-4 md:grid-cols-6">
<div className={cn(card, "md:col-span-3")}>
<Zap className="text-chart-3 size-6" aria-hidden="true" />
<h3 className="mt-5 text-xl font-semibold tracking-tight">Keyboard first</h3>
<p className="text-muted-foreground mt-2 text-pretty">Everything has a shortcut. Learn four and you rarely touch the mouse.</p>
<ul className="mt-6 space-y-2.5 text-sm">
{[["New invoice", ["⌘", "N"]], ["Jump to customer", ["⌘", "K"]], ["Mark as paid", ["⌃", "⌘", "P"]], ["Export report", ["⇧", "⌘", "E"]]].map(([label, keys]) => (
<li key={label as string} className="flex items-center justify-between">
{label as string}
<span className="flex gap-1">{(keys as string[]).map((k) => <Kbd key={k}>{k}</Kbd>)}</span>
</li>
))}
</ul>
</div>
<div className={cn(card, "md:col-span-3")}>
<Cloud className="text-chart-1 size-6" aria-hidden="true" />
<h3 className="mt-5 text-xl font-semibold tracking-tight">Local first, iCloud second</h3>
<p className="text-muted-foreground mt-2 text-pretty">Your data lives on your Mac and works with no connection. Sync across devices with your own iCloud.</p>
<div className="bg-secondary/60 mt-6 flex items-center justify-between gap-3 rounded-2xl px-5 py-4 text-sm">
<span className="flex items-center gap-2"><Laptop className="size-4" aria-hidden="true" /> MacBook Pro</span>
<span className="text-muted-foreground flex-1 border-t border-dashed" aria-hidden="true" />
<span className="text-chart-2 flex items-center gap-1.5 text-xs font-medium"><span className="bg-chart-2 size-1.5 rounded-full" aria-hidden="true" /> Synced just now</span>
<span className="text-muted-foreground flex-1 border-t border-dashed" aria-hidden="true" />
<span className="flex items-center gap-2"><Cloud className="size-4" aria-hidden="true" /> iCloud</span>
</div>
</div>
<div className={cn(card, "md:col-span-2")}>
<Lock className="text-chart-4 size-6" aria-hidden="true" />
<h3 className="mt-5 text-xl font-semibold tracking-tight">Private by design</h3>
<p className="text-muted-foreground mt-2 text-pretty">No account, no analytics, no server that holds your books.</p>
<p className="mt-6 text-5xl font-semibold tracking-[-0.04em]">0</p>
<p className="text-muted-foreground text-sm">trackers in the app</p>
</div>
<div className={cn(card, "md:col-span-4")}>
<ShieldCheck className="text-chart-2 size-6" aria-hidden="true" />
<h3 className="mt-5 text-xl font-semibold tracking-tight">Shortcuts and AppleScript</h3>
<p className="text-muted-foreground mt-2 max-w-md text-pretty">Create an invoice from a calendar event, or mark paid from a Stripe webhook. Ledger speaks the Mac’s automation language.</p>
<pre className="bg-secondary/60 mt-6 overflow-x-auto rounded-2xl p-4 font-mono text-[13px] leading-6" tabIndex={0}>{`tell application "Ledger"
set inv to make new invoice
set customer of inv to "Northwind"
mark inv as paid
end tell`}</pre>
</div>
</div>
</section>
<Devices1 title="Mac first. Everywhere else, too." />
<section aria-labelledby="ledger-reviews" className="mx-auto max-w-6xl px-4 py-20 sm:px-6">
<h2 id="ledger-reviews" className="sr-only">Reviews</h2>
<ul className="grid gap-4 md:grid-cols-3">
{reviews.map((r) => (
<li key={r.name} className={cn(card, "flex flex-col")}>
<div className="text-chart-3 flex gap-0.5" role="img" aria-label="5 out of 5 stars">{Array.from({ length: 5 }, (_, i) => <Star key={i} className="size-4 fill-current" aria-hidden="true" />)}</div>
<blockquote className="mt-4 flex-1 text-[17px] leading-relaxed text-pretty">“{r.quote}”</blockquote>
<footer className="mt-6 flex items-center gap-3 text-sm">
<span className={cn("flex size-9 items-center justify-center rounded-full text-xs font-semibold", r.tone)} aria-hidden="true">{r.name.split(" ").map((w) => w[0]).join("")}</span>
<span><span className="block font-medium">{r.name}</span><span className="text-muted-foreground">{r.role}</span></span>
</footer>
</li>
))}
</ul>
</section>
<Pricing4 title="Buy it once, or stay current." />
<section className="px-4 pb-24 text-center sm:px-6">
<AppIcon size={88} tone="blue" className="mx-auto"><span className="text-4xl font-bold">L</span></AppIcon>
<LedgerHeading className="mx-auto mt-8 max-w-xl text-4xl sm:text-5xl">Get your Monday <em>back</em>.</LedgerHeading>
<p className="text-muted-foreground mx-auto mt-4 max-w-md text-lg text-pretty">Download Ledger and try everything free for 14 days.</p>
<a href={hrefs?.download ?? "/ledger/download"} className="bg-chart-1 focus-visible:ring-ring/50 mt-8 inline-flex h-12 items-center gap-2 rounded-full px-6 font-medium text-[var(--ledger-on-accent)] outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]">
<ArrowDownToLine className="size-4" aria-hidden="true" /> Download for Mac
</a>
</section>
</main>
</LedgerShell>
)
}
export { LedgerHome, type LedgerHomeProps }// Ballmac UI: Ledger download page. https://ui.ballmac.com/templates/template-ledger
import * as React from "react"
import { Apple, Cpu, HardDrive, ShieldCheck } from "lucide-react"
import { Download1 } from "@/components/ballmac/blocks/download-1/download-1"
import { LedgerHeading, LedgerShell, type LedgerHrefs } from "@/components/ballmac/templates/ledger/ledger-theme"
const specs = [
{ icon: Apple, title: "macOS 13 Ventura or later", text: "Runs on every Mac that supports it, including the 2018 MacBook Air." },
{ icon: Cpu, title: "Apple silicon and Intel", text: "A universal app, tuned for M-series chips and still quick on Intel." },
{ icon: HardDrive, title: "58 MB to download", text: "Under 150 MB installed. Your books are a single file you can back up." },
{ icon: ShieldCheck, title: "Signed and notarized", text: "Checked by Apple and by us. SHA-256 checksums are published with every release." },
]
type LedgerDownloadProps = React.ComponentProps<"div"> & { hrefs?: Partial<LedgerHrefs> }
/** Ledger download: the download block, the system requirements and a note on updates. */
function LedgerDownload({ hrefs, ...props }: LedgerDownloadProps) {
return (
<LedgerShell page="home" hrefs={hrefs} {...props}>
<main>
<div className="mx-auto max-w-6xl px-4 pt-16 text-center sm:px-6 sm:pt-20">
<LedgerHeading as="h1" className="text-4xl sm:text-6xl">Download <em>Ledger</em>.</LedgerHeading>
<p className="text-muted-foreground mx-auto mt-4 max-w-lg text-lg text-pretty">Free for 14 days. You only need a license if you keep using it.</p>
</div>
<Download1 app="Ledger" />
<section aria-labelledby="ledger-specs" className="mx-auto max-w-6xl px-4 pb-24 sm:px-6">
<h2 id="ledger-specs" className="text-2xl font-semibold tracking-[-0.03em]">Before you download</h2>
<ul className="mt-6 grid gap-4 sm:grid-cols-2">
{specs.map((s) => (
<li key={s.title} className="bg-card flex gap-4 rounded-2xl border p-5">
<span className="bg-chart-1/12 text-chart-1 flex size-10 shrink-0 items-center justify-center rounded-xl"><s.icon className="size-5" aria-hidden="true" /></span>
<div><h3 className="font-medium">{s.title}</h3><p className="text-muted-foreground mt-1 text-sm text-pretty">{s.text}</p></div>
</li>
))}
</ul>
<p className="text-muted-foreground mt-8 text-sm text-pretty">Ledger updates itself in the background and asks before restarting. Need an older version? <a href={hrefs?.changelog ?? "/ledger/changelog"} className="text-chart-1 underline underline-offset-4">Browse every release</a>.</p>
</section>
</main>
</LedgerShell>
)
}
export { LedgerDownload, type LedgerDownloadProps }// Ballmac UI: Ledger pricing page. https://ui.ballmac.com/templates/template-ledger
import * as React from "react"
import { Check, Minus } from "lucide-react"
import { Faq2 } from "@/components/ballmac/blocks/faq-2/faq-2"
import { Pricing4 } from "@/components/ballmac/blocks/pricing-4/pricing-4"
import { LedgerHeading, LedgerShell, type LedgerHrefs } from "@/components/ballmac/templates/ledger/ledger-theme"
const rows: [string, boolean | string, boolean | string][] = [
["Invoices and expenses", "Up to 10 a month", "Unlimited"],
["Reports and exports", "Basic", "Full, with CSV and PDF"],
["iCloud sync across Macs", false, true],
["Approvals and menu bar capture", false, true],
["Shortcuts and AppleScript", false, true],
["Custom invoice templates", false, true],
["Updates", "Bug fixes", "All of this major version"],
]
function Cell({ v }: { v: boolean | string }) {
if (typeof v === "string") return <span className="text-sm">{v}</span>
return v ? <Check className="text-chart-2 mx-auto size-4.5" role="img" aria-label="Included" /> : <Minus className="text-muted-foreground mx-auto size-4" role="img" aria-label="Not included" />
}
type LedgerPricingProps = React.ComponentProps<"div"> & { hrefs?: Partial<LedgerHrefs> }
/** Ledger pricing: buy once or subscribe with the cost chart, a free-versus-licensed table and questions. */
function LedgerPricing({ hrefs, ...props }: LedgerPricingProps) {
return (
<LedgerShell page="pricing" hrefs={hrefs} {...props}>
<main>
<div className="px-4 pt-16 text-center sm:px-6 sm:pt-20">
<LedgerHeading as="h1" className="mx-auto max-w-2xl text-4xl sm:text-6xl">Honest pricing for <em>honest</em> software.</LedgerHeading>
</div>
<Pricing4 title="Pick how you like to pay." description="Buy a license and keep it forever, or subscribe if you’d rather always have the latest. The chart shows exactly when each one pays off." />
<section aria-labelledby="ledger-compare" className="mx-auto max-w-3xl px-4 pb-20 sm:px-6">
<h2 id="ledger-compare" className="text-2xl font-semibold tracking-[-0.03em]">Free trial or licensed</h2>
<div tabIndex={0} role="region" aria-label="Trial compared with a license" className="bg-card focus-visible:ring-ring/50 mt-6 overflow-x-auto rounded-2xl border outline-none focus-visible:ring-[3px]">
<table className="w-full min-w-[30rem] text-start">
<caption className="sr-only">Trial and licensed features</caption>
<thead><tr className="border-b text-sm"><th scope="col" className="p-4 font-medium"><span className="sr-only">Feature</span></th><th scope="col" className="p-4 text-center font-medium">14-day trial</th><th scope="col" className="p-4 text-center font-medium">Licensed</th></tr></thead>
<tbody>
{rows.map(([f, a, b]) => (
<tr key={f} className="border-b last:border-b-0"><th scope="row" className="p-4 text-sm font-normal">{f}</th><td className="p-4 text-center"><Cell v={a} /></td><td className="bg-chart-1/5 p-4 text-center"><Cell v={b} /></td></tr>
))}
</tbody>
</table>
</div>
</section>
<Faq2
title="Pricing questions"
description="If yours isn’t here, we answer email within a day."
categories={[
{ name: "Licenses", questions: [
{ question: "Is a license really forever?", answer: "Yes. A license works on the version you bought and every update in that major version, including all of 2.x. A new major version is a paid upgrade at a discount." },
{ question: "Can I move my license to another Mac?", answer: "Deactivate it from Settings on the old Mac and activate it on the new one. There is no limit on moves, only on how many Macs use it at once." },
] },
{ name: "Subscriptions", questions: [
{ question: "What happens if I cancel?", answer: "Ledger keeps opening your books in read-only mode, and you can export everything. You can resubscribe any time." },
{ question: "Is there a student or nonprofit discount?", answer: "Yes, 50 percent off for students, teachers and registered nonprofits. Write to us from your school or organisation address." },
] },
]}
support={{ label: "Email support", href: "#", text: "Still unsure which is right for you?" }}
/>
</main>
</LedgerShell>
)
}
export { LedgerPricing, type LedgerPricingProps }// Ballmac UI: Ledger changelog page. https://ui.ballmac.com/templates/template-ledger
import * as React from "react"
import { Changelog1 } from "@/components/ballmac/blocks/changelog-1/changelog-1"
import { LedgerHeading, LedgerShell, type LedgerHrefs } from "@/components/ballmac/templates/ledger/ledger-theme"
type LedgerChangelogProps = React.ComponentProps<"div"> & { hrefs?: Partial<LedgerHrefs> }
/** Ledger changelog: release notes with a generated cover on headline releases. */
function LedgerChangelog({ hrefs, ...props }: LedgerChangelogProps) {
return (
<LedgerShell page="changelog" hrefs={hrefs} {...props}>
<main>
<div className="px-4 pt-16 text-center sm:px-6 sm:pt-20">
<LedgerHeading as="h1" className="text-4xl sm:text-6xl">What’s <em>new</em>.</LedgerHeading>
</div>
<Changelog1 title="Every release, in plain language." description="We ship every few weeks. Here is what changed and why." />
</main>
</LedgerShell>
)
}
export { LedgerChangelog, type LedgerChangelogProps }// Ballmac UI: Ledger support page. https://ui.ballmac.com/templates/template-ledger
"use client"
import * as React from "react"
import { BookOpen, CreditCard, Keyboard, LifeBuoy, Mail, Search, Wrench } from "lucide-react"
import { Kbd } from "@/components/ballmac/kbd"
import { LedgerHeading, LedgerShell, type LedgerHrefs } from "@/components/ballmac/templates/ledger/ledger-theme"
import { cn } from "@/lib/utils"
const topics = [
{ icon: BookOpen, title: "Getting started", text: "Import your data, add a customer and send your first invoice.", articles: ["Import from a spreadsheet", "Your first invoice", "Set up iCloud sync"] },
{ icon: CreditCard, title: "Licenses and billing", text: "Activate, move or recover a license, and manage a subscription.", articles: ["Activate a license", "Move to a new Mac", "Update a payment method"] },
{ icon: Wrench, title: "Troubleshooting", text: "Fix sync conflicts, a stuck export or an app that won’t open.", articles: ["Sync says “paused”", "Rebuild the index", "Reset preferences"] },
{ icon: Keyboard, title: "Shortcuts and automation", text: "Every keyboard shortcut and a few AppleScript recipes.", articles: ["All keyboard shortcuts", "Shortcuts app actions", "AppleScript dictionary"] },
]
const shortcuts: [string, string[]][] = [
["New invoice", ["⌘", "N"]],
["Quick open", ["⌘", "K"]],
["Mark as paid", ["⌃", "⌘", "P"]],
["Export report", ["⇧", "⌘", "E"]],
["Toggle sidebar", ["⌥", "⌘", "S"]],
["Capture expense", ["⌃", "⌥", "N"]],
]
type LedgerSupportProps = React.ComponentProps<"div"> & { hrefs?: Partial<LedgerHrefs> }
/** Ledger support: a search field that filters help topics, shortcut cheat sheet and contact options. */
function LedgerSupport({ hrefs, ...props }: LedgerSupportProps) {
const [query, setQuery] = React.useState("")
const q = query.trim().toLowerCase()
const shown = q ? topics.filter((t) => [t.title, t.text, ...t.articles].join(" ").toLowerCase().includes(q)) : topics
return (
<LedgerShell page="support" hrefs={hrefs} {...props}>
<main>
<section className="px-4 pt-16 text-center sm:px-6 sm:pt-20">
<LedgerHeading as="h1" className="text-4xl sm:text-6xl">How can we <em>help</em>?</LedgerHeading>
<form role="search" onSubmit={(e) => e.preventDefault()} className="relative mx-auto mt-8 max-w-xl">
<label htmlFor="ledger-search" className="sr-only">Search help articles</label>
<Search className="text-muted-foreground pointer-events-none absolute top-1/2 start-4 size-5 -translate-y-1/2" aria-hidden="true" />
<input id="ledger-search" type="search" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search “license”, “sync”, “export”…" className="bg-card focus-visible:ring-ring/50 placeholder:text-muted-foreground h-13 w-full rounded-full border pe-5 ps-12 text-base shadow-sm outline-none focus-visible:ring-[3px]" />
</form>
<p className="text-muted-foreground mt-3 text-sm" role="status">{q ? `${shown.length} ${shown.length === 1 ? "topic" : "topics"} match “${query}”` : "Most questions are answered in under a minute."}</p>
</section>
<section aria-label="Help topics" className="mx-auto max-w-6xl px-4 py-12 sm:px-6">
{shown.length === 0 ? (
<div className="bg-card rounded-3xl border p-10 text-center"><p className="font-medium">Nothing found</p><p className="text-muted-foreground mt-1 text-sm">Try a shorter word, or write to us below.</p></div>
) : (
<ul className="grid gap-4 md:grid-cols-2">
{shown.map((t) => (
<li key={t.title} className="bg-card rounded-3xl border p-7">
<t.icon className="text-chart-1 size-6" aria-hidden="true" />
<h2 className="mt-4 text-xl font-semibold tracking-tight">{t.title}</h2>
<p className="text-muted-foreground mt-1.5 text-sm text-pretty">{t.text}</p>
<ul className="mt-5 divide-y border-t text-sm">
{t.articles.map((a) => <li key={a}><a href="#" className="hover:text-chart-1 flex items-center justify-between py-2.5 transition-colors">{a}<span aria-hidden="true">›</span></a></li>)}
</ul>
</li>
))}
</ul>
)}
</section>
<section aria-labelledby="ledger-keys" className="mx-auto max-w-6xl px-4 pb-16 sm:px-6">
<h2 id="ledger-keys" className="text-2xl font-semibold tracking-[-0.03em]">Shortcuts worth learning</h2>
<ul className="mt-6 grid gap-px overflow-hidden rounded-2xl border bg-border sm:grid-cols-2 lg:grid-cols-3">
{shortcuts.map(([label, keys]) => (
<li key={label} className="bg-card flex items-center justify-between px-5 py-4 text-sm">{label}<span className="flex gap-1">{keys.map((k) => <Kbd key={k}>{k}</Kbd>)}</span></li>
))}
</ul>
</section>
<section aria-labelledby="ledger-contact" className="mx-auto max-w-6xl px-4 pb-24 sm:px-6">
<h2 id="ledger-contact" className="sr-only">Contact</h2>
<div className="grid gap-4 md:grid-cols-2">
{[
{ icon: Mail, title: "Email us", text: "A person writes back, usually within a day.", cta: "support@ledger.app" },
{ icon: LifeBuoy, title: "System status", text: "iCloud sync and license servers are running normally.", cta: "All systems normal" },
].map((c) => (
<div key={c.title} className={cn("bg-card flex items-start gap-4 rounded-3xl border p-6")}>
<span className="bg-chart-1/12 text-chart-1 flex size-11 shrink-0 items-center justify-center rounded-2xl"><c.icon className="size-5" aria-hidden="true" /></span>
<div><h3 className="font-semibold">{c.title}</h3><p className="text-muted-foreground mt-1 text-sm text-pretty">{c.text}</p><a href="#" className="text-chart-1 mt-3 inline-block text-sm font-medium underline underline-offset-4">{c.cta}</a></div>
</div>
))}
</div>
</section>
</main>
</LedgerShell>
)
}
export { LedgerSupport, type LedgerSupportProps }// Ballmac UI: Ledger template route. https://ui.ballmac.com/templates/template-ledger
import type { Metadata } from "next"
import { LedgerHome } from "@/components/ballmac/templates/ledger/ledger-home"
export const metadata: Metadata = {
title: "Ledger: Your books, kept beautifully",
description: "A native Mac app for invoices, expenses and reports.",
}
export default function Page() {
return <LedgerHome />
}// Ballmac UI: Ledger template route. https://ui.ballmac.com/templates/template-ledger
import type { Metadata } from "next"
import { LedgerDownload } from "@/components/ballmac/templates/ledger/ledger-download"
export const metadata: Metadata = {
title: "Download Ledger",
description: "Download Ledger for Mac. Free for 14 days.",
}
export default function Page() {
return <LedgerDownload />
}// Ballmac UI: Ledger template route. https://ui.ballmac.com/templates/template-ledger
import type { Metadata } from "next"
import { LedgerPricing } from "@/components/ballmac/templates/ledger/ledger-pricing"
export const metadata: Metadata = {
title: "Ledger pricing",
description: "Buy a license once, or subscribe and always have the latest.",
}
export default function Page() {
return <LedgerPricing />
}// Ballmac UI: Ledger template route. https://ui.ballmac.com/templates/template-ledger
import type { Metadata } from "next"
import { LedgerChangelog } from "@/components/ballmac/templates/ledger/ledger-changelog"
export const metadata: Metadata = {
title: "What’s new in Ledger",
description: "Release notes for every version of Ledger.",
}
export default function Page() {
return <LedgerChangelog />
}// Ballmac UI: Ledger template route. https://ui.ballmac.com/templates/template-ledger
import type { Metadata } from "next"
import { LedgerSupport } from "@/components/ballmac/templates/ledger/ledger-support"
export const metadata: Metadata = {
title: "Ledger support",
description: "Help articles, shortcuts and ways to reach us.",
}
export default function Page() {
return <LedgerSupport />
}Installation
$ pnpm dlx shadcn@latest add @ballmac/template-ledgerInstall the dependencies.
$ pnpm add lucide-reactAdd the Ballmac items it builds on.
$ pnpm dlx shadcn@latest add @ballmac/blur-fade @ballmac/changelog-1 @ballmac/devices-1 @ballmac/download-1 @ballmac/faq-2 @ballmac/features-7 @ballmac/kbd @ballmac/mac-icons @ballmac/pricing-4 @ballmac/showcase-1Copy the source into your project.
components/ballmac/templates/ledger/ledger-fonts.ts// Ballmac UI: Ledger template fonts. https://ui.ballmac.com/templates/template-ledger import { Inter, Newsreader } from "next/font/google" /** The italic serif used for one emphasised word per heading. Body text uses the system font, like a Mac app. */ export const ledgerSerif = Newsreader({ variable: "--ledger-serif", subsets: ["latin"], style: ["normal", "italic"], display: "swap", preload: false }) /** Used where San Francisco is not installed (Windows, Linux, Android), so the page looks the same everywhere. */ export const ledgerSans = Inter({ variable: "--ledger-sans", subsets: ["latin"], display: "swap", preload: false })components/ballmac/templates/ledger/ledger-theme.tsx// Ballmac UI: Ledger template shell. https://ui.ballmac.com/templates/template-ledger "use client" import * as React from "react" import { Menu, X } from "lucide-react" import { AppIcon } from "@/components/ballmac/mac-icons" import { ledgerSans, ledgerSerif } from "@/components/ballmac/templates/ledger/ledger-fonts" import { cn } from "@/lib/utils" type LedgerPage = "home" | "download" | "pricing" | "changelog" | "support" type LedgerHrefs = Record<LedgerPage, string> const defaultHrefs: LedgerHrefs = { home: "/ledger", download: "/ledger/download", pricing: "/ledger/pricing", changelog: "/ledger/changelog", support: "/ledger/support" } /** Ledger's palette: Apple-calm. Cool white, soft grey surfaces, one blue. Dark mode follows the system. */ const ledgerCss = ` .ledger-theme{--background:oklch(0.99 0.002 250);--foreground:oklch(0.2 0.01 255);--card:oklch(1 0 0);--card-foreground:oklch(0.2 0.01 255);--popover:oklch(1 0 0);--popover-foreground:oklch(0.2 0.01 255);--primary:oklch(0.2 0.01 255);--primary-foreground:oklch(0.99 0.002 250);--secondary:oklch(0.965 0.004 250);--secondary-foreground:oklch(0.2 0.01 255);--muted:oklch(0.965 0.004 250);--muted-foreground:oklch(0.5 0.012 255);--accent:oklch(0.955 0.006 250);--accent-foreground:oklch(0.2 0.01 255);--border:oklch(0.2 0.01 255 / 10%);--input:oklch(0.2 0.01 255 / 14%);--ring:oklch(0.6 0.19 255);--surface:oklch(0.975 0.003 250);--chart-1:oklch(0.52 0.2 255);--ledger-on-accent:oklch(1 0 0);--chart-2:oklch(0.48 0.14 155);--chart-3:oklch(0.78 0.16 75);--chart-4:oklch(0.62 0.2 305);--chart-5:oklch(0.68 0.19 25);--radius:1rem} .dark .ledger-theme{--background:oklch(0.16 0.006 255);--foreground:oklch(0.96 0.004 250);--card:oklch(0.2 0.008 255);--card-foreground:oklch(0.96 0.004 250);--popover:oklch(0.22 0.008 255);--popover-foreground:oklch(0.96 0.004 250);--primary:oklch(0.96 0.004 250);--primary-foreground:oklch(0.2 0.01 255);--secondary:oklch(0.25 0.009 255);--secondary-foreground:oklch(0.96 0.004 250);--muted:oklch(0.24 0.009 255);--muted-foreground:oklch(0.72 0.012 250);--accent:oklch(0.27 0.01 255);--accent-foreground:oklch(0.96 0.004 250);--border:oklch(1 0 0 / 11%);--input:oklch(1 0 0 / 15%);--ring:oklch(0.7 0.17 255);--surface:oklch(0.18 0.007 255);--chart-1:oklch(0.72 0.15 255);--ledger-on-accent:oklch(0.16 0.01 255);--chart-2:oklch(0.76 0.16 160);--chart-3:oklch(0.83 0.15 80);--chart-4:oklch(0.72 0.17 305);--chart-5:oklch(0.76 0.16 25)} ` const links: { key: LedgerPage; label: string }[] = [ { key: "home", label: "Overview" }, { key: "pricing", label: "Pricing" }, { key: "changelog", label: "What’s new" }, { key: "support", label: "Support" }, ] type LedgerShellProps = React.ComponentProps<"div"> & { /** The page being shown, so its nav link is marked current. */ page: LedgerPage /** Override where pages live (used by previews). */ hrefs?: Partial<LedgerHrefs> } /** Ledger's frame: a frosted, centred header like a Mac app’s site, and a quiet footer. Wrap every Ledger page in it. */ function LedgerShell({ page, hrefs: overrides, className, style, children, ...props }: LedgerShellProps) { const hrefs = { ...defaultHrefs, ...overrides } const [open, setOpen] = React.useState(false) return ( <div data-slot="ledger" className={cn("ledger-theme bg-background text-foreground relative min-h-dvh overflow-x-clip antialiased", ledgerSerif.variable, ledgerSans.variable, className)} style={{ fontFamily: "-apple-system, BlinkMacSystemFont, 'SF Pro Text', var(--ledger-sans), ui-sans-serif, system-ui, sans-serif", ...style }} {...props} > <style>{ledgerCss}</style> <header className="bg-background/72 sticky top-0 z-40 border-b backdrop-blur-xl backdrop-saturate-150"> <div className="mx-auto flex h-12 max-w-6xl items-center justify-between px-4 sm:px-6"> <a href={hrefs.home} className="focus-visible:ring-ring/50 flex items-center gap-2 rounded-md text-sm font-semibold outline-none focus-visible:ring-[3px]"> <AppIcon size={22} tone="blue"><span className="text-[11px] font-bold">L</span></AppIcon> Ledger </a> <nav aria-label="Main" className="hidden items-center gap-7 md:flex"> {links.map((l) => ( <a key={l.key} href={hrefs[l.key]} aria-current={page === l.key ? "page" : undefined} className="text-muted-foreground hover:text-foreground aria-[current=page]:text-foreground focus-visible:ring-ring/50 rounded text-[13px] outline-none transition-colors focus-visible:ring-[3px]"> {l.label} </a> ))} </nav> <div className="flex items-center gap-2"> <a href={hrefs.download} className="bg-chart-1 focus-visible:ring-ring/50 inline-flex h-7 items-center rounded-full px-3.5 text-[13px] font-medium text-[var(--ledger-on-accent)] outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]">Download</a> <button type="button" aria-label={open ? "Close menu" : "Open menu"} aria-expanded={open} aria-controls="ledger-mobile-menu" onClick={() => setOpen((v) => !v)} className="text-muted-foreground focus-visible:ring-ring/50 inline-flex size-8 items-center justify-center rounded-md outline-none focus-visible:ring-[3px] md:hidden"> {open ? <X className="size-5" aria-hidden="true" /> : <Menu className="size-5" aria-hidden="true" />} </button> </div> </div> {open && ( <nav id="ledger-mobile-menu" aria-label="Mobile" className="border-t px-4 py-2 md:hidden"> {links.map((l) => <a key={l.key} href={hrefs[l.key]} className="hover:bg-accent block rounded-lg px-3 py-2.5 text-sm">{l.label}</a>)} </nav> )} </header> {children} <footer className="bg-surface border-t"> <div className="mx-auto max-w-6xl px-4 py-12 sm:px-6"> <div className="grid gap-8 sm:grid-cols-2 lg:grid-cols-4"> {[ { title: "Ledger", items: [["Overview", "home"], ["What’s new", "changelog"], ["Pricing", "pricing"], ["Download", "download"]] }, { title: "Support", items: [["Help center", "support"], ["Keyboard shortcuts", "support"], ["Contact us", "support"], ["System status", "support"]] }, { title: "Company", items: [["About", "home"], ["Press kit", "home"], ["Privacy", "home"], ["Terms", "home"]] }, { title: "Elsewhere", items: [["Mastodon", "home"], ["GitHub", "home"], ["RSS", "changelog"], ["Newsletter", "home"]] }, ].map((c) => ( <div key={c.title}> <h2 className="text-xs font-semibold">{c.title}</h2> <ul className="mt-3 space-y-2"> {c.items.map(([label, key]) => <li key={label}><a href={hrefs[key as LedgerPage]} className="text-muted-foreground hover:text-foreground text-xs transition-colors">{label}</a></li>)} </ul> </div> ))} </div> <p className="text-muted-foreground mt-10 border-t pt-5 text-xs text-pretty">Ledger requires macOS 13 Ventura or later. Apple, Mac, macOS and iCloud are trademarks of Apple Inc. © 2026 Ledger Software, made in Portland.</p> </div> </footer> </div> ) } /** A heading with one italic serif word, the template's signature. Wrap the word to emphasise in `<em>`. */ function LedgerHeading({ className, children, as: Tag = "h2", ...props }: React.ComponentProps<"h2"> & { as?: "h1" | "h2" | "h3" }) { return ( <Tag className={cn("font-semibold tracking-[-0.035em] text-balance [&_em]:font-normal [&_em]:tracking-[-0.02em] [&_em]:[font-family:var(--ledger-serif),ui-serif,Georgia,serif]", className)} {...props}> {children} </Tag> ) } export { LedgerHeading, LedgerShell, defaultHrefs as ledgerDefaultHrefs, type LedgerHrefs, type LedgerPage, type LedgerShellProps }components/ballmac/templates/ledger/ledger-home.tsx// Ballmac UI: Ledger home page. https://ui.ballmac.com/templates/template-ledger "use client" import * as React from "react" import { ArrowDownToLine, Cloud, Laptop, Lock, ShieldCheck, Star, Zap } from "lucide-react" import { Devices1 } from "@/components/ballmac/blocks/devices-1/devices-1" import { Features7 } from "@/components/ballmac/blocks/features-7/features-7" import { Pricing4 } from "@/components/ballmac/blocks/pricing-4/pricing-4" import { Showcase1 } from "@/components/ballmac/blocks/showcase-1/showcase-1" import { BlurFade } from "@/components/ballmac/blur-fade" import { Kbd } from "@/components/ballmac/kbd" import { AppIcon } from "@/components/ballmac/mac-icons" import { LedgerHeading, LedgerShell, type LedgerHrefs } from "@/components/ballmac/templates/ledger/ledger-theme" import { cn } from "@/lib/utils" const press = [ { outlet: "Mac App Store", quote: "Editor’s Choice" }, { outlet: "MacStories", quote: "The calmest way to run a business." }, { outlet: "Six Colors", quote: "A Mac app that feels like one." }, { outlet: "Daring Fireball", quote: "Fast, plain and correct." }, ] const reviews = [ { quote: "I closed the month in an afternoon. The keyboard shortcuts alone paid for the license.", name: "Hannah Okafor", role: "Freelance designer", tone: "bg-chart-1/20" }, { quote: "It’s the first accounting app that didn’t make me feel like I needed an accountant to use it.", name: "Marcus Lindqvist", role: "Founder, Studio Fjord", tone: "bg-chart-2/20" }, { quote: "Works offline on the train, syncs when I’m home. Exactly how software should behave.", name: "Priya Raman", role: "Independent consultant", tone: "bg-chart-4/20" }, ] const card = "bg-card rounded-3xl border p-7 shadow-[0_1px_2px_0_rgb(0_0_0/0.04)]" type LedgerHomeProps = React.ComponentProps<"div"> & { hrefs?: Partial<LedgerHrefs> } /** The Ledger home page: an interactive Mac desktop, a menu-bar tour, a feature bento, devices, reviews and pricing. */ function LedgerHome({ hrefs, ...props }: LedgerHomeProps) { return ( <LedgerShell page="home" hrefs={hrefs} {...props}> <main> <section className="px-4 pt-16 text-center sm:px-6 sm:pt-24"> <BlurFade> <p className="text-chart-1 text-sm font-semibold">Ledger 2.4 · Now with approvals</p> <LedgerHeading as="h1" className="mx-auto mt-4 max-w-3xl text-5xl leading-[1.02] sm:text-7xl"> Your books, kept <em>beautifully</em>. </LedgerHeading> <p className="text-muted-foreground mx-auto mt-6 max-w-xl text-lg text-pretty sm:text-xl"> Invoices, expenses and reports in a native Mac app that’s fast, private and a pleasure to open on a Monday. </p> <div className="mt-9 flex flex-col items-center justify-center gap-3 sm:flex-row sm:gap-5"> <a href={hrefs?.download ?? "/ledger/download"} className="bg-chart-1 focus-visible:ring-ring/50 inline-flex h-12 items-center gap-2 rounded-full px-6 font-medium text-[var(--ledger-on-accent)] outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]"> <ArrowDownToLine className="size-4" aria-hidden="true" /> Download for Mac </a> <a href={hrefs?.pricing ?? "/ledger/pricing"} className="text-chart-1 focus-visible:ring-ring/50 rounded-md font-medium underline-offset-4 outline-none hover:underline focus-visible:ring-[3px]"> See pricing › </a> </div> <p className="text-muted-foreground mt-5 text-xs">Free for 14 days · macOS 13 or later · Apple silicon and Intel</p> </BlurFade> <BlurFade className="mx-auto mt-14 max-w-6xl text-start" delay={0.12}> <Showcase1 app="Ledger" height="38rem" className="shadow-[0_40px_100px_-30px_rgb(0_0_0/0.45)]" /> </BlurFade> </section> <section aria-label="Press" className="mx-auto max-w-6xl px-4 py-16 sm:px-6"> <ul className="grid gap-px overflow-hidden rounded-2xl border bg-border sm:grid-cols-2 lg:grid-cols-4"> {press.map((p) => ( <li key={p.outlet} className="bg-card p-6 text-center"> <p className="text-muted-foreground text-xs font-semibold tracking-wide uppercase">{p.outlet}</p> <p className="mt-2 text-sm font-medium text-pretty [font-family:var(--ledger-serif),ui-serif,Georgia,serif] text-lg italic">“{p.quote}”</p> </li> ))} </ul> </section> <Features7 title="Always one keystroke away." /> <section className="mx-auto max-w-6xl px-4 py-20 sm:px-6"> <div className="mx-auto max-w-2xl text-center"> <LedgerHeading className="text-4xl sm:text-5xl">A real Mac app, <em>all the way down</em>.</LedgerHeading> <p className="text-muted-foreground mt-4 text-lg text-pretty">Native, not wrapped. It uses the parts of macOS you already know.</p> </div> <div className="mt-12 grid gap-4 md:grid-cols-6"> <div className={cn(card, "md:col-span-3")}> <Zap className="text-chart-3 size-6" aria-hidden="true" /> <h3 className="mt-5 text-xl font-semibold tracking-tight">Keyboard first</h3> <p className="text-muted-foreground mt-2 text-pretty">Everything has a shortcut. Learn four and you rarely touch the mouse.</p> <ul className="mt-6 space-y-2.5 text-sm"> {[["New invoice", ["⌘", "N"]], ["Jump to customer", ["⌘", "K"]], ["Mark as paid", ["⌃", "⌘", "P"]], ["Export report", ["⇧", "⌘", "E"]]].map(([label, keys]) => ( <li key={label as string} className="flex items-center justify-between"> {label as string} <span className="flex gap-1">{(keys as string[]).map((k) => <Kbd key={k}>{k}</Kbd>)}</span> </li> ))} </ul> </div> <div className={cn(card, "md:col-span-3")}> <Cloud className="text-chart-1 size-6" aria-hidden="true" /> <h3 className="mt-5 text-xl font-semibold tracking-tight">Local first, iCloud second</h3> <p className="text-muted-foreground mt-2 text-pretty">Your data lives on your Mac and works with no connection. Sync across devices with your own iCloud.</p> <div className="bg-secondary/60 mt-6 flex items-center justify-between gap-3 rounded-2xl px-5 py-4 text-sm"> <span className="flex items-center gap-2"><Laptop className="size-4" aria-hidden="true" /> MacBook Pro</span> <span className="text-muted-foreground flex-1 border-t border-dashed" aria-hidden="true" /> <span className="text-chart-2 flex items-center gap-1.5 text-xs font-medium"><span className="bg-chart-2 size-1.5 rounded-full" aria-hidden="true" /> Synced just now</span> <span className="text-muted-foreground flex-1 border-t border-dashed" aria-hidden="true" /> <span className="flex items-center gap-2"><Cloud className="size-4" aria-hidden="true" /> iCloud</span> </div> </div> <div className={cn(card, "md:col-span-2")}> <Lock className="text-chart-4 size-6" aria-hidden="true" /> <h3 className="mt-5 text-xl font-semibold tracking-tight">Private by design</h3> <p className="text-muted-foreground mt-2 text-pretty">No account, no analytics, no server that holds your books.</p> <p className="mt-6 text-5xl font-semibold tracking-[-0.04em]">0</p> <p className="text-muted-foreground text-sm">trackers in the app</p> </div> <div className={cn(card, "md:col-span-4")}> <ShieldCheck className="text-chart-2 size-6" aria-hidden="true" /> <h3 className="mt-5 text-xl font-semibold tracking-tight">Shortcuts and AppleScript</h3> <p className="text-muted-foreground mt-2 max-w-md text-pretty">Create an invoice from a calendar event, or mark paid from a Stripe webhook. Ledger speaks the Mac’s automation language.</p> <pre className="bg-secondary/60 mt-6 overflow-x-auto rounded-2xl p-4 font-mono text-[13px] leading-6" tabIndex={0}>{`tell application "Ledger" set inv to make new invoice set customer of inv to "Northwind" mark inv as paid end tell`}</pre> </div> </div> </section> <Devices1 title="Mac first. Everywhere else, too." /> <section aria-labelledby="ledger-reviews" className="mx-auto max-w-6xl px-4 py-20 sm:px-6"> <h2 id="ledger-reviews" className="sr-only">Reviews</h2> <ul className="grid gap-4 md:grid-cols-3"> {reviews.map((r) => ( <li key={r.name} className={cn(card, "flex flex-col")}> <div className="text-chart-3 flex gap-0.5" role="img" aria-label="5 out of 5 stars">{Array.from({ length: 5 }, (_, i) => <Star key={i} className="size-4 fill-current" aria-hidden="true" />)}</div> <blockquote className="mt-4 flex-1 text-[17px] leading-relaxed text-pretty">“{r.quote}”</blockquote> <footer className="mt-6 flex items-center gap-3 text-sm"> <span className={cn("flex size-9 items-center justify-center rounded-full text-xs font-semibold", r.tone)} aria-hidden="true">{r.name.split(" ").map((w) => w[0]).join("")}</span> <span><span className="block font-medium">{r.name}</span><span className="text-muted-foreground">{r.role}</span></span> </footer> </li> ))} </ul> </section> <Pricing4 title="Buy it once, or stay current." /> <section className="px-4 pb-24 text-center sm:px-6"> <AppIcon size={88} tone="blue" className="mx-auto"><span className="text-4xl font-bold">L</span></AppIcon> <LedgerHeading className="mx-auto mt-8 max-w-xl text-4xl sm:text-5xl">Get your Monday <em>back</em>.</LedgerHeading> <p className="text-muted-foreground mx-auto mt-4 max-w-md text-lg text-pretty">Download Ledger and try everything free for 14 days.</p> <a href={hrefs?.download ?? "/ledger/download"} className="bg-chart-1 focus-visible:ring-ring/50 mt-8 inline-flex h-12 items-center gap-2 rounded-full px-6 font-medium text-[var(--ledger-on-accent)] outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]"> <ArrowDownToLine className="size-4" aria-hidden="true" /> Download for Mac </a> </section> </main> </LedgerShell> ) } export { LedgerHome, type LedgerHomeProps }components/ballmac/templates/ledger/ledger-download.tsx// Ballmac UI: Ledger download page. https://ui.ballmac.com/templates/template-ledger import * as React from "react" import { Apple, Cpu, HardDrive, ShieldCheck } from "lucide-react" import { Download1 } from "@/components/ballmac/blocks/download-1/download-1" import { LedgerHeading, LedgerShell, type LedgerHrefs } from "@/components/ballmac/templates/ledger/ledger-theme" const specs = [ { icon: Apple, title: "macOS 13 Ventura or later", text: "Runs on every Mac that supports it, including the 2018 MacBook Air." }, { icon: Cpu, title: "Apple silicon and Intel", text: "A universal app, tuned for M-series chips and still quick on Intel." }, { icon: HardDrive, title: "58 MB to download", text: "Under 150 MB installed. Your books are a single file you can back up." }, { icon: ShieldCheck, title: "Signed and notarized", text: "Checked by Apple and by us. SHA-256 checksums are published with every release." }, ] type LedgerDownloadProps = React.ComponentProps<"div"> & { hrefs?: Partial<LedgerHrefs> } /** Ledger download: the download block, the system requirements and a note on updates. */ function LedgerDownload({ hrefs, ...props }: LedgerDownloadProps) { return ( <LedgerShell page="home" hrefs={hrefs} {...props}> <main> <div className="mx-auto max-w-6xl px-4 pt-16 text-center sm:px-6 sm:pt-20"> <LedgerHeading as="h1" className="text-4xl sm:text-6xl">Download <em>Ledger</em>.</LedgerHeading> <p className="text-muted-foreground mx-auto mt-4 max-w-lg text-lg text-pretty">Free for 14 days. You only need a license if you keep using it.</p> </div> <Download1 app="Ledger" /> <section aria-labelledby="ledger-specs" className="mx-auto max-w-6xl px-4 pb-24 sm:px-6"> <h2 id="ledger-specs" className="text-2xl font-semibold tracking-[-0.03em]">Before you download</h2> <ul className="mt-6 grid gap-4 sm:grid-cols-2"> {specs.map((s) => ( <li key={s.title} className="bg-card flex gap-4 rounded-2xl border p-5"> <span className="bg-chart-1/12 text-chart-1 flex size-10 shrink-0 items-center justify-center rounded-xl"><s.icon className="size-5" aria-hidden="true" /></span> <div><h3 className="font-medium">{s.title}</h3><p className="text-muted-foreground mt-1 text-sm text-pretty">{s.text}</p></div> </li> ))} </ul> <p className="text-muted-foreground mt-8 text-sm text-pretty">Ledger updates itself in the background and asks before restarting. Need an older version? <a href={hrefs?.changelog ?? "/ledger/changelog"} className="text-chart-1 underline underline-offset-4">Browse every release</a>.</p> </section> </main> </LedgerShell> ) } export { LedgerDownload, type LedgerDownloadProps }components/ballmac/templates/ledger/ledger-pricing.tsx// Ballmac UI: Ledger pricing page. https://ui.ballmac.com/templates/template-ledger import * as React from "react" import { Check, Minus } from "lucide-react" import { Faq2 } from "@/components/ballmac/blocks/faq-2/faq-2" import { Pricing4 } from "@/components/ballmac/blocks/pricing-4/pricing-4" import { LedgerHeading, LedgerShell, type LedgerHrefs } from "@/components/ballmac/templates/ledger/ledger-theme" const rows: [string, boolean | string, boolean | string][] = [ ["Invoices and expenses", "Up to 10 a month", "Unlimited"], ["Reports and exports", "Basic", "Full, with CSV and PDF"], ["iCloud sync across Macs", false, true], ["Approvals and menu bar capture", false, true], ["Shortcuts and AppleScript", false, true], ["Custom invoice templates", false, true], ["Updates", "Bug fixes", "All of this major version"], ] function Cell({ v }: { v: boolean | string }) { if (typeof v === "string") return <span className="text-sm">{v}</span> return v ? <Check className="text-chart-2 mx-auto size-4.5" role="img" aria-label="Included" /> : <Minus className="text-muted-foreground mx-auto size-4" role="img" aria-label="Not included" /> } type LedgerPricingProps = React.ComponentProps<"div"> & { hrefs?: Partial<LedgerHrefs> } /** Ledger pricing: buy once or subscribe with the cost chart, a free-versus-licensed table and questions. */ function LedgerPricing({ hrefs, ...props }: LedgerPricingProps) { return ( <LedgerShell page="pricing" hrefs={hrefs} {...props}> <main> <div className="px-4 pt-16 text-center sm:px-6 sm:pt-20"> <LedgerHeading as="h1" className="mx-auto max-w-2xl text-4xl sm:text-6xl">Honest pricing for <em>honest</em> software.</LedgerHeading> </div> <Pricing4 title="Pick how you like to pay." description="Buy a license and keep it forever, or subscribe if you’d rather always have the latest. The chart shows exactly when each one pays off." /> <section aria-labelledby="ledger-compare" className="mx-auto max-w-3xl px-4 pb-20 sm:px-6"> <h2 id="ledger-compare" className="text-2xl font-semibold tracking-[-0.03em]">Free trial or licensed</h2> <div tabIndex={0} role="region" aria-label="Trial compared with a license" className="bg-card focus-visible:ring-ring/50 mt-6 overflow-x-auto rounded-2xl border outline-none focus-visible:ring-[3px]"> <table className="w-full min-w-[30rem] text-start"> <caption className="sr-only">Trial and licensed features</caption> <thead><tr className="border-b text-sm"><th scope="col" className="p-4 font-medium"><span className="sr-only">Feature</span></th><th scope="col" className="p-4 text-center font-medium">14-day trial</th><th scope="col" className="p-4 text-center font-medium">Licensed</th></tr></thead> <tbody> {rows.map(([f, a, b]) => ( <tr key={f} className="border-b last:border-b-0"><th scope="row" className="p-4 text-sm font-normal">{f}</th><td className="p-4 text-center"><Cell v={a} /></td><td className="bg-chart-1/5 p-4 text-center"><Cell v={b} /></td></tr> ))} </tbody> </table> </div> </section> <Faq2 title="Pricing questions" description="If yours isn’t here, we answer email within a day." categories={[ { name: "Licenses", questions: [ { question: "Is a license really forever?", answer: "Yes. A license works on the version you bought and every update in that major version, including all of 2.x. A new major version is a paid upgrade at a discount." }, { question: "Can I move my license to another Mac?", answer: "Deactivate it from Settings on the old Mac and activate it on the new one. There is no limit on moves, only on how many Macs use it at once." }, ] }, { name: "Subscriptions", questions: [ { question: "What happens if I cancel?", answer: "Ledger keeps opening your books in read-only mode, and you can export everything. You can resubscribe any time." }, { question: "Is there a student or nonprofit discount?", answer: "Yes, 50 percent off for students, teachers and registered nonprofits. Write to us from your school or organisation address." }, ] }, ]} support={{ label: "Email support", href: "#", text: "Still unsure which is right for you?" }} /> </main> </LedgerShell> ) } export { LedgerPricing, type LedgerPricingProps }components/ballmac/templates/ledger/ledger-changelog.tsx// Ballmac UI: Ledger changelog page. https://ui.ballmac.com/templates/template-ledger import * as React from "react" import { Changelog1 } from "@/components/ballmac/blocks/changelog-1/changelog-1" import { LedgerHeading, LedgerShell, type LedgerHrefs } from "@/components/ballmac/templates/ledger/ledger-theme" type LedgerChangelogProps = React.ComponentProps<"div"> & { hrefs?: Partial<LedgerHrefs> } /** Ledger changelog: release notes with a generated cover on headline releases. */ function LedgerChangelog({ hrefs, ...props }: LedgerChangelogProps) { return ( <LedgerShell page="changelog" hrefs={hrefs} {...props}> <main> <div className="px-4 pt-16 text-center sm:px-6 sm:pt-20"> <LedgerHeading as="h1" className="text-4xl sm:text-6xl">What’s <em>new</em>.</LedgerHeading> </div> <Changelog1 title="Every release, in plain language." description="We ship every few weeks. Here is what changed and why." /> </main> </LedgerShell> ) } export { LedgerChangelog, type LedgerChangelogProps }components/ballmac/templates/ledger/ledger-support.tsx// Ballmac UI: Ledger support page. https://ui.ballmac.com/templates/template-ledger "use client" import * as React from "react" import { BookOpen, CreditCard, Keyboard, LifeBuoy, Mail, Search, Wrench } from "lucide-react" import { Kbd } from "@/components/ballmac/kbd" import { LedgerHeading, LedgerShell, type LedgerHrefs } from "@/components/ballmac/templates/ledger/ledger-theme" import { cn } from "@/lib/utils" const topics = [ { icon: BookOpen, title: "Getting started", text: "Import your data, add a customer and send your first invoice.", articles: ["Import from a spreadsheet", "Your first invoice", "Set up iCloud sync"] }, { icon: CreditCard, title: "Licenses and billing", text: "Activate, move or recover a license, and manage a subscription.", articles: ["Activate a license", "Move to a new Mac", "Update a payment method"] }, { icon: Wrench, title: "Troubleshooting", text: "Fix sync conflicts, a stuck export or an app that won’t open.", articles: ["Sync says “paused”", "Rebuild the index", "Reset preferences"] }, { icon: Keyboard, title: "Shortcuts and automation", text: "Every keyboard shortcut and a few AppleScript recipes.", articles: ["All keyboard shortcuts", "Shortcuts app actions", "AppleScript dictionary"] }, ] const shortcuts: [string, string[]][] = [ ["New invoice", ["⌘", "N"]], ["Quick open", ["⌘", "K"]], ["Mark as paid", ["⌃", "⌘", "P"]], ["Export report", ["⇧", "⌘", "E"]], ["Toggle sidebar", ["⌥", "⌘", "S"]], ["Capture expense", ["⌃", "⌥", "N"]], ] type LedgerSupportProps = React.ComponentProps<"div"> & { hrefs?: Partial<LedgerHrefs> } /** Ledger support: a search field that filters help topics, shortcut cheat sheet and contact options. */ function LedgerSupport({ hrefs, ...props }: LedgerSupportProps) { const [query, setQuery] = React.useState("") const q = query.trim().toLowerCase() const shown = q ? topics.filter((t) => [t.title, t.text, ...t.articles].join(" ").toLowerCase().includes(q)) : topics return ( <LedgerShell page="support" hrefs={hrefs} {...props}> <main> <section className="px-4 pt-16 text-center sm:px-6 sm:pt-20"> <LedgerHeading as="h1" className="text-4xl sm:text-6xl">How can we <em>help</em>?</LedgerHeading> <form role="search" onSubmit={(e) => e.preventDefault()} className="relative mx-auto mt-8 max-w-xl"> <label htmlFor="ledger-search" className="sr-only">Search help articles</label> <Search className="text-muted-foreground pointer-events-none absolute top-1/2 start-4 size-5 -translate-y-1/2" aria-hidden="true" /> <input id="ledger-search" type="search" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search “license”, “sync”, “export”…" className="bg-card focus-visible:ring-ring/50 placeholder:text-muted-foreground h-13 w-full rounded-full border pe-5 ps-12 text-base shadow-sm outline-none focus-visible:ring-[3px]" /> </form> <p className="text-muted-foreground mt-3 text-sm" role="status">{q ? `${shown.length} ${shown.length === 1 ? "topic" : "topics"} match “${query}”` : "Most questions are answered in under a minute."}</p> </section> <section aria-label="Help topics" className="mx-auto max-w-6xl px-4 py-12 sm:px-6"> {shown.length === 0 ? ( <div className="bg-card rounded-3xl border p-10 text-center"><p className="font-medium">Nothing found</p><p className="text-muted-foreground mt-1 text-sm">Try a shorter word, or write to us below.</p></div> ) : ( <ul className="grid gap-4 md:grid-cols-2"> {shown.map((t) => ( <li key={t.title} className="bg-card rounded-3xl border p-7"> <t.icon className="text-chart-1 size-6" aria-hidden="true" /> <h2 className="mt-4 text-xl font-semibold tracking-tight">{t.title}</h2> <p className="text-muted-foreground mt-1.5 text-sm text-pretty">{t.text}</p> <ul className="mt-5 divide-y border-t text-sm"> {t.articles.map((a) => <li key={a}><a href="#" className="hover:text-chart-1 flex items-center justify-between py-2.5 transition-colors">{a}<span aria-hidden="true">›</span></a></li>)} </ul> </li> ))} </ul> )} </section> <section aria-labelledby="ledger-keys" className="mx-auto max-w-6xl px-4 pb-16 sm:px-6"> <h2 id="ledger-keys" className="text-2xl font-semibold tracking-[-0.03em]">Shortcuts worth learning</h2> <ul className="mt-6 grid gap-px overflow-hidden rounded-2xl border bg-border sm:grid-cols-2 lg:grid-cols-3"> {shortcuts.map(([label, keys]) => ( <li key={label} className="bg-card flex items-center justify-between px-5 py-4 text-sm">{label}<span className="flex gap-1">{keys.map((k) => <Kbd key={k}>{k}</Kbd>)}</span></li> ))} </ul> </section> <section aria-labelledby="ledger-contact" className="mx-auto max-w-6xl px-4 pb-24 sm:px-6"> <h2 id="ledger-contact" className="sr-only">Contact</h2> <div className="grid gap-4 md:grid-cols-2"> {[ { icon: Mail, title: "Email us", text: "A person writes back, usually within a day.", cta: "support@ledger.app" }, { icon: LifeBuoy, title: "System status", text: "iCloud sync and license servers are running normally.", cta: "All systems normal" }, ].map((c) => ( <div key={c.title} className={cn("bg-card flex items-start gap-4 rounded-3xl border p-6")}> <span className="bg-chart-1/12 text-chart-1 flex size-11 shrink-0 items-center justify-center rounded-2xl"><c.icon className="size-5" aria-hidden="true" /></span> <div><h3 className="font-semibold">{c.title}</h3><p className="text-muted-foreground mt-1 text-sm text-pretty">{c.text}</p><a href="#" className="text-chart-1 mt-3 inline-block text-sm font-medium underline underline-offset-4">{c.cta}</a></div> </div> ))} </div> </section> </main> </LedgerShell> ) } export { LedgerSupport, type LedgerSupportProps }app/ledger/page.tsx// Ballmac UI: Ledger template route. https://ui.ballmac.com/templates/template-ledger import type { Metadata } from "next" import { LedgerHome } from "@/components/ballmac/templates/ledger/ledger-home" export const metadata: Metadata = { title: "Ledger: Your books, kept beautifully", description: "A native Mac app for invoices, expenses and reports.", } export default function Page() { return <LedgerHome /> }app/ledger/download/page.tsx// Ballmac UI: Ledger template route. https://ui.ballmac.com/templates/template-ledger import type { Metadata } from "next" import { LedgerDownload } from "@/components/ballmac/templates/ledger/ledger-download" export const metadata: Metadata = { title: "Download Ledger", description: "Download Ledger for Mac. Free for 14 days.", } export default function Page() { return <LedgerDownload /> }app/ledger/pricing/page.tsx// Ballmac UI: Ledger template route. https://ui.ballmac.com/templates/template-ledger import type { Metadata } from "next" import { LedgerPricing } from "@/components/ballmac/templates/ledger/ledger-pricing" export const metadata: Metadata = { title: "Ledger pricing", description: "Buy a license once, or subscribe and always have the latest.", } export default function Page() { return <LedgerPricing /> }app/ledger/changelog/page.tsx// Ballmac UI: Ledger template route. https://ui.ballmac.com/templates/template-ledger import type { Metadata } from "next" import { LedgerChangelog } from "@/components/ballmac/templates/ledger/ledger-changelog" export const metadata: Metadata = { title: "What’s new in Ledger", description: "Release notes for every version of Ledger.", } export default function Page() { return <LedgerChangelog /> }app/ledger/support/page.tsx// Ballmac UI: Ledger template route. https://ui.ballmac.com/templates/template-ledger import type { Metadata } from "next" import { LedgerSupport } from "@/components/ballmac/templates/ledger/ledger-support" export const metadata: Metadata = { title: "Ledger support", description: "Help articles, shortcuts and ways to reach us.", } export default function Page() { return <LedgerSupport /> }Update the import paths to match your project setup.
The page is added as a route in your app, with every block it uses.
What's inside
- Pages
- Home/ledger
- Download/ledger/download
- Pricing/ledger/pricing
- What’s new/ledger/changelog
- Support/ledger/support
- Fonts
- Newsreader, System UI (San Francisco) (Google Fonts, loaded with next/font)
- Files
- 12 files · 10 Ballmac blocks and components
Built with
Dependencies
- npm
- lucide-react
Use with AI
Installs a five-page site for a Mac or desktop app, assembled from the Mac blocks (showcase-1, features-7, devices-1, pricing-4, download-1). Change the palette in ledgerCss, and the copy in each page file. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Ledger: Mac app (@ballmac/template-ledger) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Marketing sites for Mac, desktop and indie apps
- Products that sell a license and a subscription
Not for
- Web SaaS with signup flows (use an Orbit-style template)
Registry JSON: https://ui.ballmac.com/r/template-ledger.json
Free to use in personal and commercial projects.