@matthewsreis/ui
v0.2.0
Published
Matthews UI component library
Readme
@matthewsreis/ui
UI component library for the Matthews Reis design system. Publishes React components (and future framework targets) as a single scoped package with subpath exports.
Installation
npm install @matthewsreis/uiReact 18 or later and react-dom are required as peer dependencies.
Usage
Two import styles are officially supported. They're functionally equivalent and both work with modern bundlers — pick based on the tradeoff below.
// Per-component subpath (recommended)
import { Button } from '@matthewsreis/ui/button'
import { Dialog, DialogTrigger, DialogContent } from '@matthewsreis/ui/dialog'
import { cn } from '@matthewsreis/ui/utils'
// Main barrel (convenience)
import { Button, Dialog, DialogTrigger, cn } from '@matthewsreis/ui'Tree-shaking and import patterns
Prefer per-component subpaths for production code. The package is built with bundle: false — every source file maps to exactly one dist file — so subpath imports load only the files they actually need. Importing @matthewsreis/ui/button physically touches ~7 files; importing the same component through the main barrel causes your bundler to parse the entire ~30-file barrel graph before tree-shaking it back down.
Final bundle size is identical either way for modern bundlers (Turbopack, Vite, Rollup, Webpack 5+, Bun, esbuild) — tree-shaking eliminates unused exports correctly from both paths because the package uses named re-exports only and declares precise sideEffects. The real difference is dev-time parse cost and robustness against bundlers that can't fully trust sideEffects.
Rule of thumb:
| Situation | Use |
| -------------------------------------------------------- | -------------------------------- |
| Production app, one or two components per file | @matthewsreis/ui/<component> |
| Pulling in five+ components at once (e.g. a layout file) | @matthewsreis/ui (main barrel) |
| Dev sandbox, scratch file, experimenting | Either — it doesn't matter |
Avoid runtime star patterns when re-exporting from this package. Runtime export * and import * as X force bundlers to eagerly resolve every binding in the target module to figure out what's reachable, which can defeat tree-shaking:
// Don't — runtime wildcard re-export. Consumers' bundlers now have to trace
// every export in @matthewsreis/ui even if your file uses only one.
export * from '@matthewsreis/ui'
// Don't — runtime namespace import used as a value.
import * as UI from '@matthewsreis/ui'
<UI.Button />
// Do — named re-export + named import.
export { Button } from '@matthewsreis/ui'
import { Button } from '@matthewsreis/ui'Type-only star patterns are fine. export type * and import type * as X are erased at compile time — they exist only in .d.ts output and have zero impact on the runtime bundle:
// Fine — type-only re-export, no runtime cost.
export type * from '@matthewsreis/ui/button'
// Fine — type-only namespace import.
import type * as UI from '@matthewsreis/ui'
type ButtonProps = UI.ButtonPropsThe distinction is runtime star vs type-only star. The runtime form participates in tree-shaking analysis; the type-only form is erased. This rule applies when you're authoring a file that re-exports from our package — e.g. a shared-UI wrapper in your app.
Exports
| Subpath | Resolves to |
| ------------------------------------ | ------------------------------------------------------------------ |
| @matthewsreis/ui | main barrel (every public component + cn) |
| @matthewsreis/ui/react | same as above (explicit framework alias) |
| @matthewsreis/ui/<component> | one per public component, kebab-case names |
| @matthewsreis/ui/utils | cn helper only |
| @matthewsreis/ui/tailwind-preset | shared Tailwind preset (extend in consumer's tailwind.config.ts) |
| @matthewsreis/ui/styles | compiled + minified CSS (tokens, typography, utilities all in one) |
| @matthewsreis/ui/styles/tokens | raw tokens.css (layer into your own Tailwind build à la carte) |
| @matthewsreis/ui/styles/typography | raw typography.css |
| @matthewsreis/ui/styles/utilities | raw utilities.css |
Component subpaths cover every publicly exported component: breadcrumb, button, card, checkbox, command, dialog, input, input-group, label, popover, scroll-area, sheet, switch, table, tabs, textarea, toggle, toggle-group (plus autocomplete, combobox as they stabilize).
Design conventions
Violet is reserved for AI. The violet-* palette is used only for AI features — Artemis GPT, generative UI, sparkle/AI icons, anything that signals "this is AI." Don't pick it for generic status, alerts, or decorative accents. Reach for primary, sky, indigo, or zinc instead. Components stay low-level; AI surfaces compose the violet tokens directly (bg-violet-500, text-violet-500) rather than through a dedicated ai variant.
Avoid gradients unless a Figma design explicitly calls for one. Artemis DS prohibits gradients on surfaces. The gradient-* tokens we ship exist for charts, progress bars, and specific banners that the source design specifies — not as decorative accents.
Icons: Lucide only, 2px stroke, size-icon-md (16px) inline / size-icon-lg (20px) chevrons & status / size-icon-xl (24px) nav & empty-states. Icons inherit text color.
Interaction defaults: hover = hover:bg-default-buttonFieldHover; focus = shadow-focus + border-primary-500; press darkens fill but never scales; transitions are 120–180ms ease-out (use tailwindcss-animate utilities from the preset).
Directory structure
packages/ui/
├── src/
│ └── react/ # One directory per framework - see "src/ convention" below
│ ├── components/
│ │ └── button/
│ │ ├── Button.tsx
│ │ └── Button.test.tsx
│ ├── hooks/ # Internal - exported via index.ts, NOT a top-level entry
│ └── index.ts # Public API for this framework - export everything consumers need
├── test/
│ └── setup.ts # Shared test setup (@testing-library/jest-dom/vitest)
├── dist/ # Built output (not committed)
│ └── react/
│ ├── index.js # ESM
│ ├── index.cjs # CJS
│ └── index.d.ts # Types
├── eslint.config.ts # Package-scoped ESLint flat config
├── package.json
├── tsconfig.json
├── tsup.config.ts # Auto-discovers framework entries via readdirSync + existsSync
└── vitest.config.tsPrettier config and ignore rules are inherited from ../../.prettierrc.js and ../../.prettierignore at the workspace root. ESLint config is package-scoped (eslint.config.ts).
src/ convention
Only framework root directories belong directly under src/.
tsup.config.ts treats every src/<name>/ directory that contains an index.ts as a published entry point, compiling it to dist/<name>/ and making it importable as @matthewsreis/ui/<name>. This means:
src/react/- correct, this is a framework entrysrc/vue/- correct, this would be a new framework entrysrc/hooks/- wrong - would accidentally publish@matthewsreis/ui/hooksas a standalone entry
Internal utilities, hooks, shared logic, and sub-components must live inside a framework directory and be exported through that framework's index.ts:
src/
└── react/
├── components/ # components
├── hooks/ # internal hooks, exported via index.ts
├── utils/ # internal utilities, exported via index.ts
└── index.ts # the only public surface for @matthewsreis/ui/reactCommands
Run from packages/ui/:
# Build (ESM + CJS + types for all framework entries)
pnpm build
# Watch mode
pnpm dev
# Run tests
pnpm test
pnpm test:watch
# Validate the packed tarball (publint + arethetypeswrong)
pnpm test:package
# Lint
pnpm lint
# Format
pnpm format
# Check formatting without writing
pnpm format:check
# Component sandbox (Ladle)
pnpm sandbox
pnpm sandbox:build
pnpm sandbox:previewFigma tooling (Claude Code)
This package has a paired Figma design system. To use Figma tools directly inside Claude Code (inspect designs, implement components), install the official Figma plugin for Claude:
Install the plugin
- Open Claude Code (CLI or desktop app)
- Run
/pluginsto open the plugin manager - Search for Figma and install the official Figma plugin
- Authenticate: Claude will prompt you to connect your Figma account — follow the OAuth flow
Once installed, the plugin persists across sessions. You don't need to reinstall it per project.
Note: The plugin is per-user — each dev installs it once in their own Claude account. There is no project-level auto-install for plugins. The
.mcp.jsonin this repo does not configure Figma; the plugin is the only Figma integration in use.
Available tools (Pro plan)
These tools work with our current Figma Pro plan:
| Tool | What it does |
| ---------------------------- | ----------------------------------------------------------------------------------- |
| get_design_context | Primary tool — fetches a node's design, screenshot, and code hints from a Figma URL |
| get_screenshot | Screenshot of a specific Figma node |
| get_metadata | File/component metadata |
| get_variable_defs | Design tokens and variables defined in the file |
| search_design_system | Search components across the design system |
| get_figjam | Read FigJam boards |
| generate_diagram | Create diagrams in FigJam |
| create_design_system_rules | Generate project-specific design system rules |
| use_figma | Write designs back to Figma canvas |
| create_new_file | Create a new Figma file |
| generate_figma_design | Generate a design from a description |
| whoami | Verify which Figma account is authenticated |
Gated tools (Organization/Enterprise only)
These Code Connect tools are not available on Pro and will return an error if called:
| Tool |
| ------------------------------ |
| get_code_connect_map |
| add_code_connect_map |
| get_code_connect_suggestions |
| get_context_for_code_connect |
| send_code_connect_mappings |
Typical workflow
1. Grab a Figma node URL (right-click a frame → Copy link)
2. Paste it into Claude: "implement this component: <url>"
3. Claude calls get_design_context and adapts the output to this package's patternsAdding a component
- Create
src/react/components/<name>/<Name>.tsx - Export it from
src/react/index.ts - Add tests in
<Name>.test.tsxalongside the component
Adding a new framework
To add support for a new framework (e.g., Vue), follow these steps exactly:
Create the framework directory and entry point:
src/ └── vue/ └── index.ts # public API for @matthewsreis/ui/vuetsup.config.tswill automatically detectsrc/vue/index.tson the next build — no changes totsup.config.tsneeded.Add the subpath export to
package.json:"./vue": { "types": "./dist/vue/index.d.ts", "import": "./dist/vue/index.js", "require": "./dist/vue/index.cjs" }Add peer dependencies for the new framework to
package.jsonif needed.Add a changeset and open a PR.
Note: do not create utility or shared directories (
src/hooks/,src/utils/, etc.) directly undersrc/. These belong inside the framework directory they support. See the src/ convention section above.
Build output
Each src/<framework>/index.ts compiles to its own dist/<framework>/ directory:
dist/
└── react/
├── index.js # ESM
├── index.cjs # CJS
└── index.d.ts # TypesAdding src/vue/index.ts automatically produces dist/vue/ on the next build.
