Skip to content

Getting started

Getting started

How to consume Lantern in a product repo, how to run this site, and what to do when a component is finished.

Consuming the system

Three steps, and the first two are what stop the tokens drifting between repos again.

01

Extend the preset — never copy the theme

tailwind.config.ts
// tailwind.config.ts
import btsPreset from "bts-storybook/tailwind-preset";

export default {
  presets: [btsPreset],
  content: ["./src/**/*.{ts,tsx}"],
};
02

Import the stylesheet once

main.tsx
// main.tsx — once, at the application root
import "bts-storybook/styles.css";
03

Import components from the package root

checkout-footer.tsx
import { Button } from "bts-storybook";

export function CheckoutFooter() {
  return (
    <div className="flex gap-3">
      <Button variant="tertiary">Back</Button>
      <Button>Continue to payment</Button>
    </div>
  );
}

You need both the preset and the stylesheet

The preset supplies the Tailwind theme; the stylesheet supplies the custom properties the theme points at, plus the hand-written classes (.btn-white, .nav-item, .stat-card) that several components depend on. The preset alone produces utilities that resolve to nothing.

Running Lantern

Storybook is where components are built. This site is where they are explained.

terminal
# from the monorepo root — starts Storybook on 6006 and Lantern on 3100
bun run start

# or start either process separately
bun run start:storybook
bun run start:lantern

Lantern runs on port 3100 so it can sit alongside Storybook on 6006 without a clash. The two are not alternatives — Storybook drives a component through its states in isolation; Lantern says what it is for, when to reach for it, and what the rules around it are.

How this site is wired

Worth understanding before you change it, because two of the decisions are load-bearing.

Storybook renders every catalogue preview

Lantern embeds the maintained Storybook story from localhost:6006 (or NEXT_PUBLIC_STORYBOOK_URL). It stores generated catalogue and story-id metadata, not copied component code or hand-written duplicate demos.

The Lantern shell uses a narrow source seam

Lantern itself still needs buttons, cards and other primitives for its documentation UI. Pages import them through ~/ds, which targets individual sibling Storybook modules and never the root barrel, so app-only components are not compiled.

lantern/src/ds/index.ts
// lantern/src/ds/index.ts

// Lantern's own shell uses narrow imports from sibling Storybook source.
export { Button, buttonVariants } from "@/components/ui/button";
export { cn } from "@/lib/utils";

// PENDING — stand-ins, awaiting promotion.
export * from "./pending";

Adding a finished component

Maintain the component and its examples in Storybook. Lantern's catalogue is generated and its preview remains the live Storybook iframe.

five steps
// 1. Export the component from bts-storybook/src/index.ts
export { Card } from "./components/ui/card";

// 2. Add or update its maintained Storybook story
//    bts-storybook/src/components/ui/card.stories.tsx

// 3. From the monorepo root, update Lovable and Lantern metadata
bun run component-sync:storybook

// Lantern receives generated catalogue/story metadata only.
// The rendered preview stays at http://localhost:6006.

Do not recreate the demo in Lantern

The Storybook story is the maintained example. Component sync refreshes Lantern's generated catalogue and story-id metadata; it does not copy component source, assets or a second implementation of the demo.

What makes a good story

Keep Storybook stories deterministic and cover meaningful states. Lantern shows those same stories, so there is only one example surface to maintain.

Cover both breakpoints

If the component carries a max-md: / md: pair, cover both in Storybook. Lantern's iframe viewport control renders the actual responsive tree.