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

@kairosis/eidos

v0.1.1

Published

Eidos — a React component library for dense, technical product UI.

Readme

Eidos

A React component library for dense, technical product UI — dev tools, data-heavy dashboards, and clean SaaS surfaces. Dark theme is the default; light is a first-class variant.

46 components, TypeScript throughout, CSS Modules over a Style Dictionary token pipeline.

Docs: kairosis.github.io/Eidos · Storybook

Install

npm install @kairosis/eidos

react and react-dom are peer dependencies. lucide-react ships as a dependency — it backs the Icon component.

Usage

Import the stylesheet once, at your app's entry point:

import "@kairosis/eidos/styles.css";

Then use components:

import { Button, Card, Stat, Badge } from "@kairosis/eidos";

export function Overview() {
  return (
    <Card
      title="api-gateway"
      action={
        <Badge tone="success" dot>
          Healthy
        </Badge>
      }
    >
      <Stat label="Runs today" value="1,284" delta="+12% vs last week" deltaTone="positive" />
      <Button icon="plus">Create project</Button>
    </Card>
  );
}

Theming

Dark is the default. Switch to light by setting data-theme on <html> — or on any subtree root, to scope it:

<html data-theme="light"></html>

Every token is a CSS custom property, so you can override any of them:

:root {
  --primary: #8b5cf6;
}

If you want the variables without the component styles, import the tokens alone:

import "@kairosis/eidos/tokens.css";

useTheme

useTheme drives that attribute for you, and themeScript stops the flash of the wrong theme on first paint. Use both — a hook can only run after hydration, so on its own it will still let one frame of the wrong theme through.

// In <head>, before anything paints.
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
import { useTheme, SegmentedControl } from "@kairosis/eidos";

function ThemeToggle() {
  const { theme, setTheme } = useTheme();

  return (
    <SegmentedControl
      label="Theme"
      options={["light", "dark", "system"]}
      value={theme}
      onChange={(next) => setTheme(next as Theme)}
    />
  );
}

| Returns | Type | | | ---------- | ------------------------------- | ----------------------- | | theme | "light" \| "dark" \| "system" | What the user chose | | resolved | "light" \| "dark" | What's actually applied | | setTheme | (theme: Theme) => void | |

Three states, not a boolean. "system" has to stay distinct from an explicit "dark", or someone who deliberately chose dark gets flipped to light when their OS switches at sunrise. resolved is "system" already resolved, for when you just need to know what's on screen.

Options: storageKey (default "eidos-theme"), persist (default true — turn it off if your app already owns theme state, e.g. next-themes), defaultTheme (default "system"), and element to scope the theme to a subtree instead of <html>.

There's no ThemeToggle component on purpose: the control's shape and placement is an app decision, and it's a few lines on top of the hook.

Tailwind v4

Eidos ships a Tailwind v4 theme generated from the same tokens, so utility classes and components draw from one source. In your CSS entry point:

@import "tailwindcss";
@import "@kairosis/eidos/styles.css"; /* tokens + component styles */
@import "@kairosis/eidos/tailwind.css"; /* maps the tokens into Tailwind's theme */

Order matters, and styles.css (or tokens.css) is required — the theme references the Eidos custom properties rather than duplicating their values.

Tokens then reach you as ordinary utilities:

<div className="bg-bg-surface text-text-muted border-border rounded-md shadow-md p-4">
  <span className="text-primary font-mono text-xs tracking-mono-label">LAST RUN</span>
  <Button icon="plus">Create project</Button>
</div>

| Tokens | Utilities | | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | Colors — --primary, --bg-surface, --text-muted, --tag-1…8 | bg-primary, bg-bg-surface, text-text-muted, bg-tag-3 | | Radii, type, leading, tracking, easing | rounded-md, text-lg, font-mono, leading-tight, tracking-mono-label, ease-out | | Shadows and focus rings | shadow-md, shadow-focus | | Durations | duration-fast, duration-base, duration-slow |

Colour and shadow utilities are emitted as var() references, so they follow data-theme="light" at runtime instead of freezing the dark value. Everything else is theme-invariant and inlined as a literal.

Spacing is deliberately not remapped: Tailwind's default 4px scale is already the Eidos grid, so p-4 is 16px either way. Font weights match too, so font-medium is already 500.

Components

| Group | Components | | ---------- | ------------------------------------------------------------------------------------------------------------ | | Forms | Button, IconButton, Input, Textarea, Select, Autocomplete, Checkbox, Radio, Switch, SegmentedControl, Slider | | Display | Card, Badge, TagChip, Avatar, Icon, Alert, Kbd, CodeBlock, Skeleton, EmptyState, List, Accordion, Divider | | Typography | Heading, Text, MetaLabel, DataValue | | Navigation | Tabs, Sidebar, DropdownMenu, CommandPalette, Breadcrumbs, Pagination, Wizard, Tree | | Feedback | Dialog, Toast, Tooltip, Progress, Spinner | | Data | DataTable, Sparkline, BarChart, Stat | | Layout | AppShell |

AppShell

The application frame — fixed nav column, fixed top bar, scrolling content:

<AppShell sidebar={<Sidebar … />} header={<Breadcrumbs … />}>
  <RunsPage />
</AppShell>

Layout only: it owns the height/scroll contract (which Sidebar depends on — it's height: 100% and collapses without a sized flex parent), the min-width: 0 that stops a wide DataTable from shoving the page sideways, and 100dvh for mobile Safari. Below breakpoint (default 768px) the sidebar becomes a focus-trapped drawer.

It knows nothing about routing, page titles, or what goes in the header — that's the app's business.

Icons

Icon wraps Lucide and takes a kebab-case name:

<Icon name="git-branch" size={16} />
<Button icon="plus">Create project</Button>

Names resolve against Lucide's icon map at runtime, so the whole set is available without extra imports. The trade-off is that bundlers can't tree-shake unused glyphs — if icon payload matters more to you than the string API, import Lucide components directly instead.

Use Lucide's current names: circle-check, not the deprecated check-circle-2; house, not home. An unknown name renders nothing and warns in development.

Development

npm install
npm run dev              # Storybook on :6006
npm test                 # Vitest
npm run lint             # ESLint + Stylelint
npm run lint:fix
npm run format           # Prettier
npm run tokens           # regenerate token CSS from tokens/
npm run build            # tokens -> typecheck -> dist/
npm run typecheck

CI (.github/workflows/ci.yml) runs format, lint, typecheck, test, build and Storybook on every push and PR. It also regenerates the tokens and fails if the committed CSS is stale — the generated files are committed so the package builds without the token step, and that guarantee is only true if it's enforced.

Tests

tests/ covers the behaviour the type system can't: that DataTable never reorders your rows, that Wizard refuses to skip ahead, that Autocomplete clamps its cursor when the filter narrows, that Dialog traps focus and restores it to the trigger.

tests/a11y.test.tsx runs axe over every component with real ARIA surface. Colour contrast is disabled there — jsdom has no layout or paint — and is covered instead by @storybook/addon-a11y, which runs axe in a real browser against every story.

Design tokens

tokens/ is the source of truth. Style Dictionary compiles the DTCG JSON into CSS custom properties:

tokens/primitives/palette.json                    private — never emitted
tokens/base/{typography,spacing,motion}.json  ->  src/styles/tokens/base.css   (:root)
tokens/themes/dark.json                       ->  src/styles/tokens/dark.css   (:root — dark is the default)
tokens/themes/light.json                      ->  src/styles/tokens/light.css  ([data-theme="light"])
base + dark                                   ->  src/styles/tailwind.css      (@theme inline)

Edit the JSON, then run npm run tokens. The generated CSS is committed so the package builds without the token step — never edit it by hand.

Two layers. primitives/palette.json holds the raw ramps (blue.500, neutral.850). Semantic tokens reference them and never hold a hex of their own:

// tokens/themes/dark.json
"colors": {
  "brand":  { "primary": { "$value": "{palette.blue.500}" } },
  "state":  { "success": { "$value": "{palette.green.500}" } },
  "surface": { "bg-surface": { "$value": "{palette.neutral.900}" } }
}

Change the brand blue in one place and it reaches --primary, --primary-hover and --tag-1 in both themes. Both themes draw from one neutral ramp — light is dark inverted (--text is neutral.100 in dark, neutral.850 in light), so the two can't drift apart.

Grouping doesn't leak into the CSS. colors.brand.primary still emits --primary, not --colors-brand-primary: the name transform takes the token's leaf key. Those variable names are the public API — consumers override them — so the source can be reorganised without a breaking rename. Leaf keys are written out in full (bg-surface, not surface), so grepping the JSON for a variable name lands you on the token.

The palette is filtered out of every output, and references to it are inlined as literals. References to tokens that are emitted stay as var(): --focus-ring is defined once as 0 0 0 2px var(--bg), 0 0 0 4px var(--primary) and re-resolves per theme on its own.

Light overrides only what actually changes; everything else inherits from :root.

Layout

One component per folder, named in kebab-case, grouped by category. Stories live outside src/, so they are never part of the library's compile graph or the published package:

src/
  components/
    data/
      bar-chart/
        BarChart.tsx
        BarChart.module.css
        index.ts        # the component's public API
      data-table/  sparkline/  stat/
      index.ts          # group barrel — re-exports the folders above
    display/  forms/  feedback/  navigation/  typography/
    internal/
      field/            # shared label/hint/error wrapper — not exported
  lib/                  # cx, options, tagColor, useClickOutside
  styles/
    popover.module.css  # composed by Autocomplete, DropdownMenu, CommandPalette
    tokens/             # generated — see below
  index.ts              # root barrel — re-exports the group barrels

stories/
  data/BarChart.stories.tsx
  display/  forms/  feedback/  navigation/  typography/

Exports are declared once, in the component's own index.ts, and re-exported upward — nothing restates the list.

components/internal/ holds shared building blocks that aren't public, so the category folders contain only exported components. The root barrel deliberately skips it.

Styling

Components use CSS Modules, so hover, focus and checked states are real selectors (:hover, :focus-visible, :checked + …) rather than React state. Every value comes from a token — no hardcoded colors, spacing or timings.

Each component takes className and style for escape-hatch overrides.

Relationship to claude_design/

claude_design/ is the original design-system spec: guidelines, token definitions, and reference JSX. It's the source this library was ported from, and it stays as documentation. The shipping library is src/.

The port is faithful, with three deliberate departures:

  • Icons load from lucide-react instead of an injected CDN <script>, which broke under SSR, bundlers and offline use.
  • Styles are CSS Modules instead of inline style objects — that's what makes real :hover/:focus-visible possible, and it scopes the input[type="range"] rules the original leaked globally.
  • Keyboard access is fixed where the reference dropped it: selectable List rows and Tree nodes are real buttons, and checkboxes, radios, switches and sliders now show focus rings.