Cafe Menu Board
Free Text React component — CafeMenuBoardCard. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.
Dark chalkboard-style menu with cafe header, item notes, and amber price accents — hospitality and local brand sites.
Preview
Corner & Steam
Small batch · Open till 8
Flat White
Double shot
₹180
Cold Brew
₹210
Almond Croissant
Baked daily
₹160
Matcha Latte
₹220
Setup
How to use
Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.
- 1
Run in your terminal:
$npm install clsx tailwind-merge lucide-react - 2
Copy
lib/cn.tsbelow. Skip if you already havecn(). - 3
Copy the code below and create
components/text/cafe-menu-board-card.tsxin your project. - 4
Import and render:
Exampleimport { CafeMenuBoardCard } from "@/components/text/cafe-menu-board-card";
lib/cn.ts
import { type ClassValue, clsx } from "clsx";import { twMerge } from "tailwind-merge";export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));}
components/text/cafe-menu-board-card.tsx
"use client";import { forwardRef, type ComponentPropsWithoutRef } from "react";import { cn } from "@/lib/cn";import { Coffee } from "lucide-react";export type CafeMenuItem = Readonly<{name: string;price: string;note?: string;}>;export type CafeMenuBoardCardProps = Readonly<{cafeName?: string;tagline?: string;items?: readonly CafeMenuItem[];} & ComponentPropsWithoutRef<"div">>;const DEFAULT_ITEMS: readonly CafeMenuItem[] = [{ name: "Flat White", price: "₹180", note: "Double shot" },{ name: "Cold Brew", price: "₹210" },{ name: "Almond Croissant", price: "₹160", note: "Baked daily" },{ name: "Matcha Latte", price: "₹220" },];export const CafeMenuBoardCard = forwardRef<HTMLDivElement,CafeMenuBoardCardProps>(({className,cafeName = "Corner & Steam",tagline = "Small batch · Open till 8",items = DEFAULT_ITEMS,...props},ref,) => {return (<divref={ref}data-slot="cafe-menu-board-card"className={cn("w-80 overflow-hidden rounded-3xl border border-neutral-800 bg-neutral-950 font-sans text-neutral-100 shadow-[0_18px_40px_rgba(0,0,0,0.28)]",className,)}{...props}><div className="border-b border-neutral-800 px-5 py-4"><div className="flex items-center gap-3"><div className="flex size-10 items-center justify-center rounded-2xl bg-amber-500/15 text-amber-300"><Coffee size={18} aria-hidden /></div><div><h3 className="text-base font-semibold tracking-tight">{cafeName}</h3><p className="text-xs text-neutral-400">{tagline}</p></div></div></div><ul className="space-y-0 px-5 py-4">{items.map((item, index) => (<likey={item.name}className={cn("py-3",index < items.length - 1? "border-b border-neutral-800/80": undefined,)}><div className="flex items-start justify-between gap-3"><div className="min-w-0"><p className="text-sm font-medium text-neutral-100">{item.name}</p>{item.note ? (<p className="mt-0.5 text-[11px] text-neutral-500">{item.note}</p>) : null}</div><p className="shrink-0 text-sm font-semibold text-amber-300">{item.price}</p></div></li>))}</ul></div>);},);CafeMenuBoardCard.displayName = "CafeMenuBoardCard";