Skip to content
NYCU-WinLabPublic

About

WinLab design system: a lightweight shadcn registry served at ui.winlab.tw

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

63 Commits

Folders and files

Repository files navigation

██╗    ██╗██╗███╗   ██╗██╗      █████╗ ██████╗     ██╗   ██╗██╗
██║    ██║██║████╗  ██║██║     ██╔══██╗██╔══██╗    ██║   ██║██║
██║ █╗ ██║██║██╔██╗ ██║██║     ███████║██████╔╝    ██║   ██║██║
██║███╗██║██║██║╚██╗██║██║     ██╔══██║██╔══██╗    ██║   ██║██║
╚███╔███╔╝██║██║ ╚████║███████╗██║  ██║██████╔╝    ╚██████╔╝██║
 ╚══╝╚══╝ ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═════╝      ╚═════╝ ╚═╝

WinLab UI

One design system for every WinLab app, installed with the shadcn CLI.

CI  Live  License: MIT

WinLab apps used to pick their own colors, spacing and component variants. This registry is the single source for all of them: a lightweight set of tokens and components, each with only the variants our apps actually use.

Browse it live

Use it

Create a Next.js app, then init it from the WinLab base:

npx create-next-app@latest my-app
cd my-app
npx shadcn@latest init https://ui.winlab.tw/r/base.json

The base sets the style to winlab, so every component comes from this registry: a bare npx shadcn@latest add button fails on purpose. (init -t next also fails, because the template adds shadcn's own button.)

This writes the WinLab tokens into app/globals.css and registers the @winlab namespace in components.json, so later components install with:

npx shadcn@latest add @winlab/button

What is in the registry

Item Type What it gives you
base registry:base Ultramarine brand tokens (Pantone 286 C, #0033A0) on a neutral scale, light and dark; success and warning status colors; --radius: 1rem; the font stacks below
font-inter registry:font Inter for Latin text and numbers
font-noto-sans-jp registry:font Noto Sans JP for kanji and punctuation (Japanese forms: ,。 sit in the lower left)
font-noto-sans-tc registry:font Noto Sans TC for the Traditional Chinese characters Noto Sans JP lacks
font-jetbrains-mono registry:font JetBrains Mono for code and IDs, ligatures on
button registry:ui 40px tall; variants default, outline, ghost, destructive; sizes default, icon
label registry:ui Form label
input registry:ui Text input, same height as button
textarea registry:ui Multi-line input that grows with its content
skeleton registry:ui Loading placeholder
dialog registry:ui Modal; sizes default (512px) and wide (672px), scrolls past 92% of the viewport height
alert-dialog registry:ui Confirmation with Cancel and an action
select registry:ui Single-choice dropdown, same height as input
table registry:ui Data table with muted headers; add tabular-nums and text-right to number columns
popover registry:ui Frosted menu surface anchored to a trigger; w-(--anchor-width) matches the trigger
command registry:ui Searchable list for a combobox inside a popover
combobox registry:ui ComboboxTrigger (same field look as select) and ComboboxContent (as wide as the trigger) around a command
dropdown-menu registry:ui Action menu: items, labels, separators and submenus
calendar registry:ui One month of 40px days to pick a single date; weeks start on Monday, captions read 2026 年 10 月
attachment registry:ui One file per row: 40px icon or thumbnail, name, a detail line and actions; states idle (dashed), uploading, error, done
checkbox registry:ui Round 20px checkbox
switch registry:ui On / off toggle, 40 x 24
tabs registry:ui Segmented tabs
avatar registry:ui Round 40px avatar with initials fallback
tooltip registry:ui Short inverted label on hover or focus
separator registry:ui Hairline divider
collapsible registry:ui Unstyled show / hide region
sonner registry:ui Toasts: frosted surface, status-colored icons, action and cancel buttons
app-shell registry:block The four corners of every WinLab app and the page layouts column, spotlight and wide; set NEXT_PUBLIC_BUILD_SHA, _TIME and _URL at build for the version corner
confirm-dialog registry:block Asks before an action runs; the confirm button names the verb, both buttons lock while it runs, and it closes only when the action finishes
form-dialog registry:block A short form in a dialog; FormField puts a label over its control, fields and buttons lock while it submits, and it closes only when the submit finishes
empty-state registry:block What a list shows with no rows: "還沒有{noun}" or "找不到符合「{query}」的{noun}", with the next action; TableEmpty is the same sentence as a table row
page-header registry:block The top of a page: text-title title, and the page's own actions on the right (under the title on a phone); SectionHeader is the same one level down
list-skeleton registry:block Placeholder rows for a list (ListSkeleton) or a table body (TableSkeleton) on first load only, split by the same dividers as the real rows
member-combobox registry:block Pick lab members by name or email; one member closes on choice, multiple keeps the menu open and toggles
field-list registry:block One record's fields as a dl: muted names in a left column, values beside them (under them on a phone), split by dividers
status-page registry:block A spotlight page that says what happened and the way out: not found, an error, no access; the site's own 404 uses it
date-picker registry:block A date field with the same closed look as select; opens a calendar, closes on choice, can block days, and posts yyyy-mm-dd under name for form-dialog
action-panel registry:block A page whose job is one action, such as opening the door: one 192px round button that locks while it runs, turns success for a moment, and says why when it cannot be pressed
file-upload registry:block Drop, paste or pick files; chosen files list as attachment rows to remove, files outside accept or maxSize show as errors with the reason, and name posts them inside form-dialog
badge registry:ui Status label; variants default, muted, outline, destructive, success, warning

Text falls back in that order: font-sans is Inter, then Noto Sans JP, then Noto Sans TC. font-mono puts JetBrains Mono in front of the same CJK fonts. Use Inter's tabular-nums for amounts and table figures, not font-mono.

Type scale

Two sizes, named by role:

Utility Size / line height Use
text-title 24px / 32px Page and container titles
text-body 16px / 24px Everything else; secondary text uses text-muted-foreground

The base clears Tailwind's default scale (--text-*: initial), so text-sm, text-2xl and the rest generate nothing. Components added to this registry use text-title and text-body only. Hierarchy below a title comes from weight, the muted color and layout patterns, not size.

bun run tokens:check rejects anything that bypasses the tokens in this repo: the old scale names, arbitrary values (rounded-[10px], text-[13px]), Tailwind palette colors (bg-blue-500), color literals, inline styles and raw font-size.

Note

A project created with -t next keeps the template's Geist imports in app/layout.tsx. They are unused by the WinLab stacks; delete them to skip the extra download.

Design rules

The two-layer model, type, radius and color rules are in DESIGN.md.

Tech stack

Layer Choice
Framework Next.js 16 (App Router, static export)
Registry shadcn CLI (shadcn build)
Styling Tailwind CSS v4 + Base UI
Package manager Bun

Getting started

git clone https://github.com/NYCU-WinLab/ui && cd ui
bun install
bun dev

Registry items are declared in registry.json. bun run build runs shadcn build into public/r/ and then builds the site.

The site's own app/globals.css must match the base item's cssVars; bun run theme:check fails when they drift.

Deploy

The site is a static export served by nginx. Each push to main publishes ghcr.io/nycu-winlab/ui:main and ghcr.io/nycu-winlab/ui:sha-<commit>.

cp .env.example .env   # pin UI_VERSION to a sha- tag in production
docker compose pull && docker compose up -d

Contributing

Issues and PRs welcome: start with CONTRIBUTING.md.

License

MIT · Ultramarine, all the way down.

About

WinLab design system: a lightweight shadcn registry served at ui.winlab.tw

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages