npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@comitor/ui

v1.10.0

Published

Comitor design system — Shadcn/UI tùy biến theo brand Comitor + component dùng chung cho hệ sinh thái SaaS (Tasks, Chat, CRM, HR…)

Readme

@comitor/ui

Hệ thống thiết kế dùng chung của hệ sinh thái Comitor (Tasks, Chat, CRM, HR, admin console, marketing site). Đóng gói shadcn/ui style new-york đã tùy biến theo brand Comitor, cộng thêm các component ghép sẵn và khung ứng dụng (shell) kiểu workspace × app.

  • React 19 · Tailwind v4 (CSS-first) · TypeScript strict · ESM-only
  • Token brand nằm ở styles.css — đổi token, bump version, mọi app nhận cùng một thay đổi.
  • Server-component-friendly: "use client" chỉ nằm ở file thật sự cần.

Trạng thái: 1.8.0 — 52 file primitive (Tier 1) · 31 file composite (Tier 2) · 25 file shell (Tier 3) · 4 file chart · 5 file uploader. API chốt từ 1.0.0.

Tài liệu đầy đủ: https://design-system.comitor.aiGet started · Components · Composites · Shell · Changelog · npm. README này là bản tra nhanh: cạm bẫy, hợp đồng màu, ranh giới server/client.

API đã chốt ở 1.0.0: từ đây thay đổi phá vỡ tương thích đi kèm bump major, không còn đi kèm minor như dải 0.x — mỗi mục breaking vẫn có bảng chuyển đổi ở changelog.


1. Cài đặt

pnpm add @comitor/ui
# peer bắt buộc
pnpm add react react-dom lucide-react
pnpm add -D tailwindcss @tailwindcss/postcss
# peer optional — cài khi dùng entry tương ứng
pnpm add next next-themes   # cho @comitor/ui/shell
pnpm add recharts           # cho @comitor/ui/chart
pnpm add react-hook-form    # cho @comitor/ui/form
pnpm add @uppy/core @uppy/xhr-upload   # cho @comitor/ui/uploader

| Peer | Range | Bắt buộc | Cần khi | |---|---|---|---| | react, react-dom | ^19.0.0 | ✅ | luôn | | tailwindcss | ^4.1.0 | ✅ | luôn (styles.css phải được TW4 biên dịch) | | lucide-react | >=0.540.0 | ✅ | luôn (tránh 2 bản icon trong bundle) | | next | >=15 | — | /shell | | next-themes | >=0.4 | — | /shell (context singleton — app và package phải dùng cùng một bản) | | recharts | >=2.15.0 | — | /chart (kiểu đã dựng cho cả 2.x lẫn 3.x, xem ghi chú dưới) | | react-hook-form | ^7.54.0 | — | /form (context singleton) | | @uppy/core | ^6.0.0 | — | /uploader | | @uppy/xhr-upload | ^6.0.0 | — | /uploader |

Vì sao rechartslucide-react>= chứ không phải ^. ^2.15.0 loại thẳng recharts 3 — mà 3.x chạy tốt với gói này: kiểu của tầng /chart không dẫn xuất payload/label từ TooltipProps/LegendProps (3.x đã bỏ hai trường đó khỏi props) mà tự khai ChartPayloadItem, phần giao của hai bản. Đã chạy typecheck + build trên cả recharts 2.15.4 lẫn 3.10.1. lucide-react cũng vậy: thang phiên bản của nó nhảy 0.x1.x mà API icon không đổi, đã kiểm với 1.33.0.

Hiệu ứng vẽ dần của recharts 2.15.x hỏng với React 19.2, nên ComitorAreaChart / ComitorBarChart / ComitorLineChart mặc định tắt hiệu ứng (§4.2). Nếu tự lắp recharts trong <ChartContainer> thì gói không tắt hộ được — app phải tự ghi isAnimationActive={false} lên từng <Bar>/<Line>/<Area> (§4.2).

Cài đặt từng bước, kèm bản mẫu app: https://design-system.comitor.ai/get-started

2. Nạp styles.css — ĐỌC KỸ, đây là chỗ dễ sai nhất

styles.css phải nằm trong app/globals.css, ngay sau @import "tailwindcss" — không phải import trong layout.tsx (layout.tsx chỉ import globals.css của chính app):

/* app/globals.css */
@import "tailwindcss";
@import "@comitor/ui/styles.css";

KHÔNG viết import "@comitor/ui/styles.css" trong layout.tsx (thói quen Tailwind 3). File này chứa @theme / @custom-variant / @source — những at-rule chỉ có nghĩa khi được Tailwind biên dịch. Import ở layout.tsx thì biến CSS vẫn chạy nhưng không utility nào (bg-gold-300, dark:*…) được sinh ra → giao diện vỡ mà không có lỗi build.

Dùng pnpm strict mà thấy mất sạch style thì khai thêm ở app: @source "../node_modules/@comitor/ui/dist"; (gói đã tự khai @source "./dist"; Tailwind v4 mặc định bỏ qua node_modules).

Font: app tự nạp Inter (subsets latin + vietnamese) và gán --font-inter; gói chỉ tiêu thụ. --font-inter-mono là tuỳ chọn, gán y hệt cách gán --font-inter nếu muốn đổi font chữ đều (số trong tooltip biểu đồ, Kbd…). Không gán thì mỗi biến rơi về fallback khai sẵn trong styles.css. Cách gán bằng next/font: https://design-system.comitor.ai/get-started

Con trỏ: styles.css khai lại cursor: pointer cho button, [role="button"]label[for]@layer base. Preflight của Tailwind v3 có luật này, v4 bỏ nó để bám mặc định trình duyệt — nên thiếu nó thì <button><a> trông giống hệt nhau lại có con trỏ khác nhau, và một nút ghost đọc ra như một cái nhãn. App không cần rải cursor-pointer; control bị vô hiệu (:disabled hoặc aria-disabled="true") vẫn giữ con trỏ mặc định.

Dark mode theo class (@custom-variant dark (&:is(.dark *))), khớp next-themes. Lưu ý: biến thể dark chỉ khớp con cháu của .dark, không khớp chính phần tử mang class đó. Muốn dựng preview 2 theme cạnh nhau phải bọc thêm một lớp: <div class="dark"><div>…</div></div>. Cách dựng ThemeProvider, thứ tự lồng với ContrastProvider, và suppressHydrationWarning bắt buộc ở thẻ <html>: https://design-system.comitor.ai/shell

3. Entry point

Package có 6 entry, tách theo peer optional mà mỗi tầng kéo theo:

| Entry | Nội dung | Peer optional kéo theo | |---|---|---| | @comitor/ui | Tier 1 (primitive) + Tier 2 (composite) + cn + token TS + useIsMac | không có — chạy được ở Vite, Storybook, test runner | | @comitor/ui/shell | Tier 3: khung ứng dụng workspace × app, theme, ba trục hiển thị | next, next-themes | | @comitor/ui/chart | Lớp shadcn bọc recharts + Comitor*Chart + CHART_COLORS | recharts (~100KB) | | @comitor/ui/uploader | FileUpload (tệp tài liệu) · ImageUploadField (ảnh đại diện, logo) · useFileUpload | @uppy/core, @uppy/xhr-upload (~13KB gzip) | | @comitor/ui/form | Binding shadcn ⇄ react-hook-form | react-hook-form | | @comitor/ui/tokens | Token dạng object TS — cho nơi không có Tailwind: canvas, email HTML, PDF | không có |

Một số tên xuất hiện ở nhiều hơn một entry, và export * chỉ báo đỏ khi trùng trong cùng một entry — trùng xuyên entry thì im lặng hoàn toàn. (Đếm chính xác bao nhiêu tên thì tuỳ cách đếm — "cùng ký tự" hay "cùng binding sau khi giải alias" cho hai kết quả khác nhau — nên đừng ghim một con số ở đây. Bất biến cần giữ: cùng tên ⇒ cùng một binding.) Đúng 2 trong số đó là hai cài đặt khác nhau (FormField / FormFieldProps: ở Tier 2 là bố cục form 12 cột, ở /form là wrapper của Controller); 18 tên còn lại trỏ về cùng một binding nên lấy ở đâu cũng như nhau. Bảng đầy đủ, kèm hai ca không trùng tên hay bị tưởng nhầm (ToasterSonnerToaster, và useIsMac chỉ xuất ở @comitor/ui): https://design-system.comitor.ai/get-started

4. Cạm bẫy hay gặp

4.1 Shell — hai trục, và mục menu phân biệt nhau bằng query

Shell dựng trên hai trục điều hướng: WorkspaceSwitcher đổi ngữ cảnh dữ liệu (Acme Corp ▸ Beta Ltd), AppLauncher đổi sản phẩm trong cùng workspace (Tasks ▸ Chat ▸ CRM ▸ HR). Cả hai không nhận dữ liệu qua prop — chúng đọc context của ShellProviderAppShell đã bọc sẵn, nên workspaces / apps / nav chỉ truyền một lần cho AppShell.

usePathname() của Next không bao giờ chứa ?query. Vì vậy nhiều mục cùng đường dẫn (/cai-dat?tab=a, ?tab=b) sẽ cùng sáng một lúc — không lỗi, không warning. Đưa query cho shell qua prop optional searchParams thì mỗi tab sáng đúng phần của nó.

Cách lắp, toàn bộ luật active matching, và bản dùng ngoài Next: https://design-system.comitor.ai/shell · https://design-system.comitor.ai/components/sidebar-nav

4.2 Biểu đồ — hiệu ứng vẽ dần, và ranh giới server/client

⚠ Với recharts 2.15.x + React 19.2, hiệu ứng vẽ dần không bao giờ chạy tới khung hình cuối, nên mark đứng nguyên ở khung đầu — tức là vô hình: đủ trục, đủ lưới, đủ chú giải, KHÔNG có dữ liệu, và chờ bao lâu cũng không tự hết. ComitorAreaChart / ComitorBarChart / ComitorLineChart vì thế mặc định tắt hiệu ứng. Nhưng khi tự lắp recharts trong <ChartContainer> thì mark là children do app viết, container không đặt hộ được — app phải tự ghi isAnimationActive={false} lên từng <Bar> / <Line> / <Area>. Dải peer của gói là >=2.15.0 nên cứ ghi: trên 3.x lỗi không tồn tại và prop chỉ còn nghĩa "không hoạt hình". https://design-system.comitor.ai/components/chart

Ranh giới server/client là cạm bẫy cùng họ, và hỏng theo hai kiểu ngược nhau: 7 helper dựng chuỗi class (buttonVariants, toggleVariants…) gọi từ Server Component thì ném lỗi to, còn một hằng dữ liệu lọt vào sau file "use client" thì ra undefinedkhông throw, không warning — trong khi TypeScript vẫn khai kiểu literal. Danh sách đầy đủ 97 tên, ai gọi được ai không: https://design-system.comitor.ai/foundations/server-client

5. Bảng component

Số file nguồn hiện tại: 52 primitive · 31 composite · 25 shell · 4 chart · 5 uploader. Mỗi component có trang riêng kèm demo, bảng prop và cạm bẫy của nó:

| Tầng | Là gì | Trang | |---|---|---| | Tier 1 — primitive · 52 file · @comitor/ui | shadcn/ui style new-york, đã tùy biến token theo brand Comitor | https://design-system.comitor.ai/components | | Tier 2 — composite · 31 file · @comitor/ui | Giá trị thật của gói: pattern lặp ở mọi app, ghép từ nhiều primitive (DataTable, PageHeader, Combobox, DatePicker, StatCard…) | https://design-system.comitor.ai/composites | | Tier 3 — shell · 25 file · @comitor/ui/shell | Khung ứng dụng workspace × app: sidebar, header, theme, ba trục hiển thị (tương phản · mật độ · cỡ chữ) | https://design-system.comitor.ai/shell | | chart · 4 file · @comitor/ui/chart | Lớp shadcn bọc recharts, cùng ba bản dựng sẵn theo brand. ⚠ Ba wrapper Comitor*Chart mặc định TẮT hiệu ứng vẽ dần (isAnimationActive = false) — bật lại là biểu đồ kẹt ở khung đầu: có trục, có lưới, KHÔNG có dữ liệu. Mặc định đó KHÔNG áp cho <ChartContainer> + recharts do app tự lắp | https://design-system.comitor.ai/components/chart | | uploader · 5 file · @comitor/ui/uploader | FileUpload (tệp) · ImageUploadField (ảnh) · useFileUpload — kéo thả, xem trước, tiến độ. Peer optional: @uppy/core + @uppy/xhr-upload | https://design-system.comitor.ai/components | | form · @comitor/ui/form | Binding shadcn ⇄ react-hook-form | https://design-system.comitor.ai/components/form |

Cơ chế labels — đổi ngôn ngữ mà không fork component. Chuỗi hiển thị mặc định của gói là tiếng Việt, kể cả chuỗi chỉ trình đọc màn hình nghe thấy (aria-label, sr-only); app dùng ngôn ngữ khác override qua prop labels. Kiểu là Partial<> và component luôn trải { ...DEFAULT_*, ...labels }, nên truyền một khoá không làm mất phần còn lại. Component chỉ có một hai chuỗi thì mở bằng prop rời thay cho cả một object — clearLabel, pendingLabel, overflowLabel, placeholder, emptyText: cùng cơ chế, chỉ khác hình dạng. ⚠ Khoá có số/đơn vị chèn vào là HÀM, không phải chuỗi có placeholder (range(start, end, total, unitLabel), pageStatus(page, pageCount), overflowLabel(count)) — nối chuỗi bằng dấu cộng ở phía app là cách chắc chắn sai trật tự từ với ngôn ngữ khác.

6. Màu và token

Nguồn token là @comitor/ui/styles.css (ship kèm gói, mở được ở node_modules/@comitor/ui/styles.css); bản mirror TS là entry @comitor/ui/tokens. Hai hợp đồng dưới đây là phần quan trọng nhất của gói — sai một trong hai thì hỏng im lặng, không có lỗi build.

1. Có HAI bảng màu, dùng chung một bộ component. Mặc định là bảng của comitor-ds — design system đã được duyệt, chính là bảng trên trang tài liệu, và là nguồn sự thật về hình thức. Bật <html data-contrast="high"> thì đổi sang bảng đã đo WCAG, dành cho màn hình độ sáng thấp, màn hình rẻ, hoặc dùng ngoài trời. Bảng mặc định cố ý có những cặp dưới ngưỡng: đó là quyết định của người duyệt, không phải lỗi bỏ sót.

Điều kiện để hai bảng cùng sống: mọi component phải đi qua token vai trò (--control-on, --control-edge, --choice-edge, --switch-track, --slider-track, --input-fill, --primary-ink, --link-ink…) chứ không viết thẳng bg-gold-300 / bg-ash-300 / text-gold-400. Viết thẳng thang màu là khoá cứng một bảng, và bảng kia lặng lẽ sai.

2. Mỗi màu có BA vai trò — đọc tên là biết dùng ở đâu:

| Tên | Vai trò | Đo tương phản với | Ví dụ dùng | |---|---|---|---| | --x | tô NỀN — fill đặc hoặc tint /10/25 | — | bg-teal, bg-teal/15 | | --x-foreground | chữ/icon NẰM TRÊN nền --x đặc | --x | bg-destructive text-destructive-foreground | | --x-ink | chính màu đó khi đi lên nền TRANG: chữ, icon rời, viền, vạch active | --background / --card / --muted, hoặc tint của chính nó | text-teal-ink, border-red-ink |

Phải có bậc -ink riêng vì bản light của màu phụ được chọn để tô nền nên quá nhạt cho vai trò chữ — --teal trên tint 15% của chính nó chỉ 3,44:1 ✗, còn --teal-ink được 5,54:1 ✓. Ở dark không có bậc riêng: --x-ink trỏ về var(--x), nhờ vậy text-teal-ink viết một lần là đúng cả hai theme, không cần biến thể dark:. ⚠ Chọn sai vai trò thì không có gì báo: utility text-teal / text-app-accent vẫn được Tailwind sinh ra như thường.

App ghi đè --app-accent thì PHẢI set kèm --app-accent-ink (và --app-accent-foreground nếu accent sáng), khai bằng CSS có nhánh .dark chứ không phải inline style — một giá trị inline không có nhánh dark. Gói cố ý không dẫn xuất -ink bằng color-mix: dẫn xuất tự động cho ra con số tương phản không ai kiểm. Cùng luật đó khi đổi --teal / --green / --red — chúng là hex đã đóng băng, không tự đi theo.

Bảng token đầy đủ, bản đối chiếu viết-sai ⇄ viết-đúng, cách bật bảng tương phản cao, và mọi số đo: https://design-system.comitor.ai/foundations/colors

7. Kiểm định trước mỗi lần phát hành

Mỗi bản publish đi qua cùng một chuỗi cổng (prepublishOnly nối chúng lại): typecheck → build → verify:directivessmoke:entriescheck:exportstest:nav-activetest:grid-contracttest:a11y-gatelint:a11y. Những cổng đáng để người dùng gói biết là chúng tồn tại:

  • lint:a11y — hợp đồng màu. Quét toàn bộ src/**/*.{ts,tsx} theo QUY TẮC (không theo danh sách phát hiện) và đối chiếu với styles.css: token TÔ NỀN đem làm màu chữ/icon/viền là sai vai trò, và mọi cặp chữ/nền phải đạt 4,5:1 (chữ) hoặc 3:1 (thành phần phi văn bản) ở cả light lẫn dark. Bảng màu là một TRỤC (từ 1.6.0): cổng đo cả bốn tổ hợp bảng × theme, nhưng chỉ CHẶN ở bảng tương phản cao. Bảng mặc định vào một sổ riêng, IN RA mỗi lần chạy nhưng không làm đỏ — nó là bản duyệt và đã được chấp nhận với đúng những con số dưới ngưỡng ở §6; bắt nó là bắt một quyết định, và một cổng lúc nào cũng đỏ thì lần sau không ai chạy nữa. ⚠ Cổng xanh vì thế KHÔNG có nghĩa bảng mặc định đạt — đọc khối "BẢNG MÀU MẶC ĐỊNH: N cặp dưới ngưỡng" mà nó in ra, cùng khối "Bốn lớp nhiều nhất" ngay dưới — đọc output, đừng đọc con số ở đây: nó đổi theo mỗi lần chạm token. Hình dạng của khoản nợ thì ổn định, và đó mới là thứ đáng nhớ: gần ba phần tư nằm ở đúng text-muted-foreground, nên "chặn được chúng" nghĩa là đổi MỘT token và đổi vẻ ngoài mọi app. Trước 1.6.0 cổng còn không ĐO bảng mặc định lần nào, tức nó in ✓ cho một bảng nó chưa từng nhìn. Ở bảng tương phản cao thì mọi loại phát hiện đều exit 1, kể cả lớp dùng bảng màu mặc định của Tailwind và miễn trừ đã thừa; miễn trừ mỗi mục bắt buộc kèm lý do bằng chữ.
  • verify:directives — ranh giới server/client. File nguồn nào mở đầu bằng "use client" thì file dist/ tương ứng phải mở đầu y hệt (thiếu file build cũng tính là hỏng), và không hằng dữ liệu thuần nào được kẹt trong file client (§4.2).
  • smoke:entries nạp cả 6 entry bằng Node ESM thuần, qua đúng exports map (§3).
  • test:a11y-gate tự kiểm chính cổng màu (cổng còn bắt đủ không); test:nav-active khoá toàn bộ luật active matching của sidebar dưới dạng hàm thuần (§4.1).

Quy ước "use client" của gói: đặt per-file, ở dòng đầu tiên, và directive được giữ nguyên trong dist/ → Next.js đánh đúng ranh giới server/client. Module thuần dữ liệu (lib/cn.ts, tokens.ts, shell/types.ts) không mang directive nên đọc được từ Server Component (§4.2).

App tiêu thụ chạy được cùng cổng màu trên bảng token đã ghi đè của mình: script nhận COMITOR_UI_STYLES / COMITOR_UI_SRC trỏ sang file của app. Nhưng script không nằm trong tarball npm — hợp đồng màu đầy đủ và các giới hạn đã biết ở https://design-system.comitor.ai/foundations/colors

8. Giấy phép

MIT © Comitor