journal-ds
v1.0.0
Published
A warm, editorial design system — cream paper, deep ink, burgundy accents, serif typography.
Readme
Journal Design System
A warm, editorial component library built with React, Radix UI, and Tailwind v4. Cream paper · deep ink · burgundy accents · Playfair Display + Lora.
What's inside
| Export | What you get |
|---|---|
| journal-ds | All 40+ UI, editorial, navigation, and layout components |
| journal-ds/hooks | useToast hook |
| journal-ds/utils | cn() class-merging utility |
| journal-ds/styles | CSS custom property tokens + Tailwind @theme mappings |
Installation
Option A — Local path (recommended while iterating)
From your other project's root:
npm install ../path/to/journal-dsExample — if your projects sit side by side on the Desktop:
# your-project/
npm install "../New folder/journal-ds"npm will symlink the package so any npm run build:lib in journal-ds
is immediately reflected without reinstalling.
Option B — npm pack (stable snapshot)
# inside journal-ds/
npm run build:lib
npm pack
# produces journal-ds-1.0.0.tgz
# inside your-project/
npm install ../New\ folder/journal-ds/journal-ds-1.0.0.tgzOption C — Publish to npm
# inside journal-ds/
npm run build:lib
npm publish --access publicThen in any project:
npm install journal-dsSetup in your project
Your project needs React 18+, Tailwind v4, and Next.js 14+ (or any React framework).
1. Import the styles
In your global CSS file (e.g. src/app/globals.css or src/index.css):
/* Pull in all design tokens and Tailwind theme mappings */
@import "journal-ds/styles";This gives you:
- All CSS custom properties (
--background,--primary,--journal-burgundy, etc.) - Light and dark theme (
.darkclass) - Tailwind
@thememappings sobg-primary,text-journal-burgundy,font-displayetc. all work - Editorial utility classes (
journal-vignette,journal-lined,journal-drop-cap, etc.)
2. Wrap your app with ThemeProvider
In your root layout:
// app/layout.tsx
import { ThemeProvider } from "next-themes";
import "journal-ds/styles"; // or via globals.css @import above
import { Lora, Playfair_Display, JetBrains_Mono } from "next/font/google";
const lora = Lora({ subsets: ["latin"], variable: "--font-lora", display: "swap" });
const playfair = Playfair_Display({ subsets: ["latin"], variable: "--font-playfair", display: "swap" });
const jetbrains = JetBrains_Mono({ subsets: ["latin"], variable: "--font-jetbrains", display: "swap" });
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning
className={`${lora.variable} ${playfair.variable} ${jetbrains.variable}`}>
<body>
<ThemeProvider attribute="class" defaultTheme="light" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}3. Add the Toaster (for toast notifications)
import { Toaster } from "journal-ds";
// inside your layout <body>:
<Toaster />Usage examples
Buttons
import { Button } from "journal-ds";
<Button>Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Delete</Button>
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>Typography
import { Heading, Text, Caption, Eyebrow } from "journal-ds";
<Eyebrow>Volume I</Eyebrow>
<Heading level="h1">On Slow Mornings</Heading>
<Text variant="lead">A lead paragraph set in Lora at 1.125rem.</Text>
<Text>Body copy with 1.8 line height — comfortable for long-form reading.</Text>
<Caption variant="meta">June 29, 2026 · 4 min read</Caption>Cards
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "journal-ds";
import { Button } from "journal-ds";
<Card withVignette>
<CardHeader>
<CardTitle>Entry title</CardTitle>
<CardDescription>A short description of the card.</CardDescription>
</CardHeader>
<CardContent>Card body content goes here.</CardContent>
<CardFooter>
<Button variant="outline" size="sm">Cancel</Button>
<Button size="sm">Save</Button>
</CardFooter>
</Card>Forms
import { Label, Input, Textarea, Select, SelectTrigger, SelectValue,
SelectContent, SelectItem, Checkbox, Switch } from "journal-ds";
<div className="space-y-1.5">
<Label htmlFor="title">Title</Label>
<Input id="title" placeholder="Entry title..." />
</div>
<div className="space-y-1.5">
<Label htmlFor="body">Body</Label>
<Textarea id="body" rows={6} placeholder="Start writing..." />
</div>
<Select>
<SelectTrigger><SelectValue placeholder="Choose mood..." /></SelectTrigger>
<SelectContent>
<SelectItem value="reflective">Reflective</SelectItem>
<SelectItem value="hopeful">Hopeful</SelectItem>
</SelectContent>
</Select>Toast notifications
"use client";
import { useToast } from "journal-ds/hooks";
import { Button } from "journal-ds";
export function SaveButton() {
const { toast } = useToast();
return (
<Button onClick={() => toast({
title: "Saved!",
description: "Your entry has been saved.",
variant: "success",
})}>
Save entry
</Button>
);
}Dialog (modal)
import { Dialog, DialogTrigger, DialogContent, DialogHeader,
DialogTitle, DialogDescription, DialogFooter } from "journal-ds";
import { Button } from "journal-ds";
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Delete entry</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>This action cannot be undone.</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline">Cancel</Button>
<Button variant="destructive">Delete</Button>
</DialogFooter>
</DialogContent>
</Dialog>Editorial components
import { DropCap, PullQuote, Blockquote, OrnamentalDivider,
LinedPaper, JournalCard } from "journal-ds";
// Drop cap paragraph
<DropCap color="burgundy">
There is something to be said for the unhurried morning...
</DropCap>
// Pull quote
<PullQuote attribution="On Slow Mornings, 2026">
The morning is the door of the day, and serenity is its key.
</PullQuote>
// Blockquote variants
<Blockquote variant="editorial" attribution="Virginia Woolf">
A woman must have money and a room of her own.
</Blockquote>
// Ornamental dividers
<OrnamentalDivider variant="simple" />
<OrnamentalDivider variant="ornamental" glyph="❉" />
<OrnamentalDivider variant="dot" />
// Lined paper container
<LinedPaper withMarginLine className="p-6 min-h-40 rounded border border-border">
<p className="leading-[32px]">Write something here...</p>
</LinedPaper>Layout
import { Container, Stack, HStack, VStack, Grid } from "journal-ds";
<Container size="md">
<VStack gap="lg">
<HStack gap="sm" align="center">
<span>Left</span>
<span>Right</span>
</HStack>
<Grid cols={2} gap="md">
<div>Column 1</div>
<div>Column 2</div>
</Grid>
</VStack>
</Container>cn() utility
import { cn } from "journal-ds/utils";
<div className={cn("base-class", isActive && "active", className)} />Dark mode
The library uses next-themes with attribute="class". Adding class="dark" to <html> swaps all tokens automatically. Use the built-in toggle:
import { ThemeToggle } from "journal-ds";
<ThemeToggle /> // icon button with dropdown
<ThemeToggle variant="full" /> // light / dark / system pillRebuilding after changes
Whenever you edit components in journal-ds, rebuild the library:
# inside journal-ds/
npm run build:libIf installed via local path (npm install ../journal-ds), the consuming project picks up changes immediately on next dev server restart. No reinstall needed.
Full component list
UI — Accordion, Alert, Avatar, Badge, Button, Card, Checkbox, Collapsible, Command, ContextMenu, Dialog, DropdownMenu, Input, Label, Popover, Progress, RadioGroup, ScrollArea, Select, Separator, Sheet, Skeleton, Slider, Spinner, Switch, Table, Tabs, Textarea, ThemeToggle, Toast, Toaster, Tooltip, Typography (Heading / Text / Caption / Eyebrow)
Editorial — Blockquote, DropCap, JournalCard, LinedPaper, OrnamentalDivider, PaperContainer, PullQuote
Navigation — Breadcrumb, Navbar, Pagination, Sidebar
Layout — Container, Grid, Stack / HStack / VStack
License
MIT © Hein Min Thant
