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

@twin.org/ui-kit

v0.0.2

Published

UI component kit and design system

Readme

@twin.org/ui-kit

UI component library and design system for Twin and Tlip, built with React and Tailwind CSS v4.

Setup

pnpm add @twin.org/ui-kit

Import the styles in your app entry point. Pre-built bundle:

import '@twin.org/ui-kit/styles';

Or the source CSS for Tailwind v4 consumers (your project's Tailwind compiles it):

@import '@twin.org/ui-kit/styles/index.css';

Add the Tailwind v4 Vite plugin:

// vite.config.ts
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [tailwindcss()]
});

Theming

Themes are activated by a single {theme}-{slot} class on the <html> element. Slot names are free-form kebab-case identifiers — the maintainer picks them when wiring a Figma mode into COLLECTIONS. The authoritative list of every available activation class is exported as THEMES from the package root.

import { THEMES } from '@twin.org/ui-kit';

const allClasses = THEMES.flatMap(t => t.classes);
// e.g. ["twin-light", "twin-dark", "tlip-light", "tlip-dark"]

function setTheme(themeClass: string) {
  document.documentElement.className = themeClass;
}

The Tailwind dark: variant fires only for slots literally named dark. Slots with any other name (light, pinky, colourblind, etc.) do not participate in dark: and are activated only by their explicit class.

Contract: themes are activated on <html> only. Do not nest theme classes inside the document — the dark: variant uses descendant selectors and would fire incorrectly inside a nested theme.

Adding a new theme

Add an entry to the COLLECTIONS array in tokens/scripts/generate-tokens.ts. The shape adapts to whatever slots the brand actually has:

// Multi-mode brand, light + dark
{
  name: 'NewBrand Brand Colours',
  role: 'primitives',
  theme: 'newbrand',
  modes: { light: 'NewBrand Light', dark: 'NewBrand Dark' }
}

// Single-mode dark-leaning brand (dark: variant fires)
{
  name: 'Shimmer Brand Colours',
  role: 'primitives',
  theme: 'shimmer',
  modes: { dark: 'Shimmer' }
}

// Brand with non-light/dark modes (no dark: variant participation)
{
  name: 'App3 Brand Colours',
  role: 'primitives',
  theme: 'app3',
  modes: { pinky: 'App3 Pinky', colourblind: 'App3 Colourblind' }
}

The first declared theme — and the first declared slot inside it — is the canonical default for tools that need to pick one (Storybook initial theme, the script's "next steps" hint).

Re-run the pipeline; CSS files, palette constants, manifest, and the Storybook switcher all update automatically.

Adding a slot to an existing theme

Purely additive. If Shimmer adds a light variant later:

{
  name: 'Shimmer Brand Colours',
  role: 'primitives',
  theme: 'shimmer',
  modes: { dark: 'Shimmer Dark', light: 'Shimmer Light' }
}

The existing <html class="shimmer-dark"> keeps working unchanged. A new shimmer-light class becomes available. No consumer migration needed.

Design tokens

Tokens are defined in Figma and exported via the variables2css plugin.

Token categories

| Category | CSS variable example | Usage in components | | ---------------- | -------------------------------------- | -------------------------------------------------- | | Brand buttons | --brand-surface-button-primary | bg-brand-surface-button-primary | | Backgrounds | --background-surface-bg-main | bg-background-surface-bg-main | | Text | --base-text-default | text-base-text-default | | Borders | --border-surface-border-primary | border-border-surface-border-primary | | Alerts | --alerts-surface-error | bg-alerts-surface-error | | Primitives | --color-neutral-950 | bg-neutral-950, text-twin-brand-primary-orange | | Spacing scale | --spacing-8 (0.5rem) | p-8, gap-8, h-32 | | Semantic spacing | --spacing-standard-element-spacing-s | gap-[var(--spacing-standard-element-spacing-s)] |

Semantic tokens resolve to the correct value for the active brand and mode automatically — no extra logic needed in components.

Updating tokens

1. Export from Figma

Open the Figma file and run the variables2css plugin. Export the variables and save the output to:

packages/ui-kit/tokens/figma.json

Replace the entire file content.

2. Run the generator

pnpm [email protected]/ui-kit tokens:generate

This reads figma.json and generates:

All outputs land under tokens/generated/ — one folder, easy to wipe and regenerate.

| Output file | Description | | -------------------------------------------- | ------------------------------------------------------------------ | | tokens/generated/index.css | Entrypoint that imports the rest and registers the dark: variant | | tokens/generated/primitives.css | Tailwind @theme for primitive + brand palette (--color-*) | | tokens/generated/themes/{theme}/{slot}.css | Per-theme, per-slot semantic tokens on :root.{theme}-{slot} | | tokens/generated/spacing.css | Tailwind @theme for spacing (scale + responsive) | | tokens/generated/ui-styles.css | Tailwind @theme for UI tokens (--radius-*, --stroke-*) | | tokens/generated/theme-inline.css | Tailwind @theme inline mapping semantic vars to color utilities | | tokens/generated/colors.constants.ts | TypeScript palette constants + THEMES manifest |

The script is strict by design: any drift between Figma and the configured contract (missing collections, unresolved aliases, unknown UI-style groups, multiple modes in a single-mode collection, etc.) is a non-zero exit and no files are written. Combined with git status in CI, that's enough to detect "designer changed Figma without us noticing."

3. Commit the generated files

git add tokens/generated
git commit -m "chore: sync design tokens"

Dry run

To preview what would be generated without writing files:

pnpm [email protected]/ui-kit tokens:generate:dry

Storybook

pnpm [email protected]/ui-kit dev

The Storybook toolbar has two switchers:

  • Brand (paintbrush icon): Toggle between Twin and Tlip
  • Mode (circle icon): Toggle between Light and Dark

Testing

pnpm test          # watch mode
pnpm test:run      # single run (CI)
pnpm test:coverage # with coverage report

Test files live next to the component they test:

src/components/button/
├── Button.tsx
└── Button.test.tsx   ← here

What every component test should cover

| What | Why | | ----------------------------- | ------------------------------------------------------------------- | | Renders its content | Catches broken imports, wrong exports, or a crash on mount | | disabled blocks interaction | This is explicit contract, disabled buttons must not fire events | | Event handlers fire | Verifies the wiring between props and DOM events | | HTML attributes are forwarded | Ensures aria-label, type, data-* reach the underlying element |

For components with richer logic, also cover props that conditionally show or hide parts of the UI, keyboard interactions, and controlled/uncontrolled state changes.

What to skip

| What | Why | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | One test per variant or size | Variants are visual, if primary and secondary render the same structure, there is nothing to test twice. Cover them in Storybook | | CSS class assertions | toHaveClass('bg-brand-...') is not a behaviour test. Wrong classes break visuals, not functionality | | Snapshot tests | Fail on every unrelated markup change and rarely catch real bugs | | React edge cases (null children, undefined props) | The framework handles those, don't test React |

Component conventions

Disabled state

Always use the native disabled prop. Never add a custom isDisabled prop — even for non-interactive elements like div, accept disabled and map it to aria-disabled.

// ✅ correct
<ButtonGroupItem disabled />
<TableRowCheckbox disabled />

// ❌ wrong
<ButtonGroupItem isDisabled />
<TableRowCheckbox isDisabled />

Boolean props

All boolean props use the is* prefix. Never use has* or show*.

// ✅ correct
isIconVisible;
isLabelVisible;
isNotificationVisible;

// ❌ wrong
showIcon;
hasIcon;
showLabel;

Size props

All size enums use the sm / md / lg scale (extended with xs and xl where needed). Never use small, base, large, or regular as enum keys.

// ✅ correct
export enum ButtonSize {
  Sm = 'sm',
  Md = 'md',
  Lg = 'lg'
}

// ❌ wrong
export enum ButtonSize {
  Small = 'small',
  Base = 'base',
  Large = 'large'
}

Components that only have two sizes use the two most appropriate steps from the scale (e.g. Sm / Md, Md / Lg).

Classes files

All exports in *.classes.ts files that contain CSS classes must end with _CLASSES (plural). Exports that hold non-CSS values (numbers, plain text strings) are exempt.

// ✅ correct
export const VARIANT_CLASSES: Record<BadgeVariant, string> = { ... };
export const TOGGLE_SIZE_CLASSES: Record<ToggleSize, string> = { ... };
export const INDICATOR_BASE_CLASSES = [...];
export const ICON_SIZE = 18; // number — no _CLASSES suffix

// ❌ wrong
export const VARIANT_STYLES: Record<BadgeVariant, string> = { ... };
export const TOGGLE_SIZES: Record<ToggleSize, string> = { ... };
export const INDICATOR_BASE = [...];

Record keys must always use enum members, never string literals:

// ✅ correct
export const VARIANT_CLASSES: Record<BadgeVariant, string> = {
  [BadgeVariant.Default]: '...',
  [BadgeVariant.Success]: '...'
};

// ❌ wrong
export const VARIANT_CLASSES: Record<BadgeVariant, string> = {
  default: '...',
  success: '...'
};

Available components

| Component | Import | | --------- | ------------------------------------------- | | Button | import { Button } from '@twin.org/ui-kit' |

Icons

Icons are sourced from the Phosphor Icons library and bundled directly — no extra install needed.

Usage

import { User, Bell, ArrowRight } from '@twin.org/ui-kit';

<User width={24} type="regular" />
<Bell width={20} type="bold" color="#000" />

Props:

| Prop | Type | Default | | ----------- | ----------------------------------------------------- | -------------- | | width | number \| string | 24 | | height | number \| string | same as width | | type | thin \| light \| regular \| bold \| fill \| duotone | regular | | color | string | currentColor | | className | string | — |

Adding a new icon

# 1. Add the icon to the manifest
pnpm [email protected]/ui-kit icons:add --export-name ArrowLeft --phosphor-name ArrowLeft

# 2. Regenerate the wrappers
pnpm [email protected]/ui-kit icons:generate

--export-name is the public name used in imports. --phosphor-name is the exact component name from @phosphor-icons/react.

Regenerating all icons

If you edit src/icons/iconManifest.ts directly:

pnpm [email protected]/ui-kit icons:generate

Development

# Start Storybook
pnpm dev

# Build the package
pnpm build

# Generate design tokens from figma.json
pnpm tokens:generate

# Dry-run token generation (no files written)
pnpm tokens:generate:dry

# Add a new icon
pnpm icons:add --export-name <PascalCase> --phosphor-name <PascalCase>

# Regenerate icon wrappers
pnpm icons:generate

# Type check
pnpm typecheck

# Lint
pnpm lint