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

@ekanos/ui

v0.18.0

Published

Ekanos-styled UI primitives for building Ekanos integrations outside the monorepo. A curated, published slice of the internal @kit/ui library.

Readme

@ekanos/ui

Ekanos-styled UI primitives for building Ekanos integrations outside the monorepo. This is the published companion to @ekanos/sdk: a curated slice of the internal @kit/ui library, chosen from what the SDK's re-exported components and the example integration packages actually import.

Docs: sdk.ekanos.dev/ui/overview.

Inside the monorepo, keep importing @kit/ui/* directly — this package exists for partners who can't.

What's exported

One subpath per component, mirroring the @kit/ui names:

| Subpath | Contents | | ---------------------------- | ------------------------------------------------------------------------------------------------------------- | | @ekanos/ui/button | Button, ButtonProps, buttonVariants | | @ekanos/ui/card | Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, … | | @ekanos/ui/badge | Badge, badgeVariants | | @ekanos/ui/status-badge | StatusBadge (neutral pill + tone dot), StatusTone | | @ekanos/ui/stat | Stat, StatLabel, StatFigure, StatValue, StatChange, StatDetail | | @ekanos/ui/page | PageBody, PageRail, PageTitleSection, PageToolbar, PageToolbarActions, PageRailTabs, PageRailSubTabs, PageBackButton, PageRangeSwitch, PageViewModeToggle | | @ekanos/ui/tooltip | Tooltip, TooltipTrigger, TooltipContent, TooltipProvider | | @ekanos/ui/select | Select, SelectTrigger, SelectValue, SelectContent, SelectItem, … | | @ekanos/ui/form | Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage, useFormField | | @ekanos/ui/input | Input | | @ekanos/ui/textarea | Textarea | | @ekanos/ui/switch | Switch | | @ekanos/ui/dropdown-menu | DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, … | | @ekanos/ui/label | Label | | @ekanos/ui/skeleton | Skeleton | | @ekanos/ui/icon | Icon, renderIcon, resolveIcon, Font Awesome helpers | | @ekanos/ui/utils | cn(), isRouteActive(), checkIfRouteIsActive() | | @ekanos/ui/if | If conditional renderer | | @ekanos/ui/trans | Trans (next-intl; needs a NextIntlClientProvider) | | @ekanos/ui/spinner | Spinner | | @ekanos/ui/ai-prompt-input | PromptInput compound components for AI ask surfaces | | @ekanos/ui/table | Table, TableHeader, TableBody, TableRow, TableHead, TableCell, … (static tabular facts) | | @ekanos/ui/data-table | DataTable, DataTableColumn and its sort/paging types (the house table: sorting, paging, row clicks) | | @ekanos/ui/empty-state | EmptyState, EmptyMedia, EmptyStateHeading, EmptyStateText, EmptyStateButton (a whole empty surface) | | @ekanos/ui/empty-note | EmptyNote (an empty card or section inside a populated surface) | | @ekanos/ui/tabs | Tabs, TabsList, TabsTrigger, TabsContent | | @ekanos/ui/sheet | Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, … | | @ekanos/ui/confirm-dialog | ConfirmDialog | | @ekanos/ui/search-field | SearchField |

Most source files are a thin re-export of the internal @kit/ui library — for those, @kit/ui stays the single source of truth. At build time that code is inlined into dist/ so the published artifact has no workspace dependencies; pnpm --filter @ekanos/ui pack:test fails the build if any @kit/ specifier leaks into the tarball.

Five subpaths — if, trans, spinner, empty-state and data-table — plus PageBody in page, are instead implemented natively here. Their internal counterparts are Makerkit-authored code from the commercial Turbo starter this monorepo was built on; publishing that under this package's MIT license would purport to grant reuse rights we do not hold. The implementations shipped here are original, and pack:test fails the build if any Makerkit-authored source is ever inlined into dist/.

Styling

Every component emits Tailwind class strings bound to Ekanos's semantic design tokens (bg-primary, text-muted-foreground, border-border, shadow-ring, …), so something has to define those. This package ships that CSS.

Pick one of the two paths. Loading both means two copies of every utility.

You do NOT use Tailwind — import the compiled stylesheet

import '@ekanos/ui/styles.css';

Self-contained: Tailwind's Preflight, the design tokens, light and dark, and every utility class the components in this package emit — all pre-compiled. Nothing else to install, no build step, no config. Works in Next.js, Vite, CRA, a plain <link rel="stylesheet">, anything.

You DO use Tailwind v4 — import the preset

@import 'tailwindcss';
@import '@ekanos/ui/theme.css';

That registers the token map (--color-primary → --primary, the --radius-* scale, shadow-ring), the token values for both themes, the base rules, the dark: variant, and an @source pointing at this package's bundles so your Tailwind build generates the components' utilities itself. You then get the Ekanos tokens in your own class strings too.

Also published, if you want the pieces separately: @ekanos/ui/tokens.css (just the custom properties, no Tailwind at-rules) and @ekanos/ui/base.css (the base rules).

What styles.css does and does not contain

It contains the utilities this package's components emit — derived by compiling every class string found in the built bundles. It is not a copy of Tailwind.

So <Button> and <Card> render correctly, but a Tailwind class you write in your component (p-8, grid-cols-3, h-5 w-5) has no rule in it and will do nothing. That is inherent: a pre-compiled stylesheet cannot know class names that don't exist yet. Two options, both fine:

  • style your own markup with plain CSS or inline styles, or
  • take the Tailwind path above, which is how Ekanos itself works.

styles.css includes Tailwind's Preflight, which is a global reset — it will normalise margins, headings and form controls across your whole page. If that is unwelcome, use the Tailwind path and control layering yourself.

Page background and dark mode

The stylesheet will not repaint your page uninvited. To adopt Ekanos's page surface — which is what makes dark mode look right rather than dark cards floating on a white page — add the opt-in class:

<body class="ekanos-root">

Dark mode resolves in three states, in this order:

| State | Result | | ----------------------------------------- | ---------------------------- | | nothing set | light | | @media (prefers-color-scheme: dark) | dark | | .dark or [data-theme='dark'] ancestor | dark, beating the media query | | .light or [data-theme='light'] on :root | light, beating the media query |

So a partner app with no theme toggle at all still follows the OS, and one with a toggle (next-themes, or your own) works by setting the class. Ekanos itself is class-only; the media query is added here for apps that have no switcher.

Tokens: generated from Ekanos's design system

tokens.css is generated from Ekanos's base theme (the token stylesheet the Ekanos app itself uses), so it carries the full semantic set: surfaces, brand, status with their -strong text steps, chart, sidebar, assistant, --favorite, the whole elevation scale and --radius. Only the header and the partner font stacks are hand-written; everything below the GENERATED marker is rewritten by:

pnpm --filter @ekanos/ui sync:tokens          # after changing a token in the app
pnpm --filter @ekanos/ui sync:tokens --check  # what CI's gate runs

The admin design-system page runs the sync for you when it saves. Never edit a value in tokens.css directly: the next sync overwrites it, and until then the build and the package tests fail on the difference.

The compiled styles.css still prunes to exactly the tokens the components use.

Fonts

Ekanos sets everything in Inter, and the assistant's replies in Newsreader, loaded via next/font. This package only names them in --font-sans / --font-heading / --font-voice, followed by a system fallback stack — no webfont is bundled or fetched. For exact typographic parity, load them yourself:

<link
  rel="stylesheet"
  href="https://fonts.googleapis.com/css2?family=Inter:opsz,[email protected],100..900&family=Newsreader:opsz,[email protected],200..800&display=swap"
/>

Without them, everything still renders — in your platform's UI font.

Icons: you must supply Font Awesome

Icon renders Font Awesome markup (<i class="fa-solid fa-house">) and this package ships no icon font, because Ekanos uses Font Awesome Pro (7.2.0, © Fonticons, Inc.) under a commercial licence. Redistributing it inside an MIT package would hand reuse rights we do not hold to everyone who installs this; pack:test fails the build if any font data ever reaches dist/.

If you hold a Font Awesome Pro licence, load your own Pro CSS (or kit) and every icon renders exactly as it does in Ekanos.

If you do not, use Font Awesome Free — but read this, because the fit is imperfect:

npm install @fortawesome/fontawesome-free
import '@fortawesome/fontawesome-free/css/all.min.css';

Free ships fa-solid, fa-regular and fa-brands. It has no fa-light or fa-duotone family, and it is missing many Pro glyphs — and fa-light is exactly what @kit/ui's resolver falls back to for an icon name it doesn't recognise. Force everything into a family Free actually has:

import { IconStyleOverrideProvider } from '@ekanos/ui/icon';

<IconStyleOverrideProvider value="solid">{children}</IconStyleOverrideProvider>;

Any glyph Free still doesn't have falls back to a question mark rather than vanishing (--ekanos-icon-fa-fallback, default '\f059') — and in development, Icon / resolveIcon / renderIcon console.warn once per distinct name that isn't in Free's set, so you find out from your terminal instead of a screenshot review. Check a name yourself with pnpm --filter @ekanos/ui check:fa-free-names (it verifies the generated src/_impl/fa-free-icon-names.generated.ts against the installed @fortawesome/fontawesome-free; run it with --write after bumping that package).

If you load no Font Awesome at all, icons do not disappear silently: this package's CSS draws a placeholder box (□) in their place, so "I forgot the icon font" is visibly different from "my code is broken". Change or disable it with --ekanos-icon-fallback.

Note that FA glyphs are sized by font-size, not box dimensions, and Icon derives that from the first h-* / w-* / size-* token in className in JS — so <Icon className="h-5 w-5" /> is the right visual size even on the no-Tailwind path where those utilities have no CSS rule.

Runtime notes

  • Primitives are Base UI, not Radix. Everything popup-shaped is built on @base-ui/react (declared dependency). No asChild — polymorphic parts take a render prop: <DropdownMenuTrigger render={<Button />}>Menu</DropdownMenuTrigger>.

  • Trans resolves against the host's next-intl messages. It is a client component that reads next-intl's context, so it must render under a NextIntlClientProvider. The Ekanos host mounts one at its root and @ekanos/harness's RootLayout mounts one for you; anywhere else (a Storybook, a bare unit test) wrap the tree in one. Empty messages are enough, because a missing key renders defaults:

    <NextIntlClientProvider locale="en" messages={{}} timeZone="UTC">
      <Trans i18nKey="myIntegration:connect" defaults="Connect {{service}}" values={{ service: 'Drive' }} />
    </NextIntlClientProvider>

    Keys keep the ns:key shape and defaults keep {{var}} interpolation. A key resolves to the host's message when it exists; otherwise defaults (interpolated, with components), then children, then the key itself. With no provider at all, Trans throws. The old react-i18next build instead rendered its children silently.

  • Spinner is self-contained. It draws an inline SVG rather than a Font Awesome glyph, so unlike Icon it needs no icon web font — only the Tailwind utilities it emits (animate-spin, size-*, text-*). It honours prefers-reduced-motion via motion-reduce:animate-none, freezing the arc into a static busy glyph.

  • 'use client' is preserved. Client entries (icon, form, tooltip, label, select, dropdown-menu, ai-prompt-input, dialog, separator) ship with the directive at the top of their bundle. utils, if, trans, and spinner are server-safe and carry no directive, so cn() etc. remain usable from server components.

  • ai is a types-only dependency. @ekanos/ui/ai-prompt-input uses ChatStatus / FileUIPart types from the ai package in its public props; no ai code is imported at runtime.

  • Base UI patch divergence (minor). The monorepo patches @base-ui/[email protected] to widen a trigger drag-boundary offset (BOUNDARY_OFFSET 2 → 24 in menu/select/combobox triggers). The published artifact resolves the unpatched package from npm, so partner-side menus keep upstream's slightly tighter press-and-drag selection boundary. Cosmetic only.

Peer dependencies

You provide: react / react-dom (^19), react-hook-form and @hookform/resolvers (for form usage). Everything else the components need at runtime is a declared dependency of this package.

Building / verifying

pnpm --filter @ekanos/ui build      # tsdown → dist/, then the CSS build → the stylesheets
pnpm --filter @ekanos/ui pack:test  # clean-room publish gate

The build runs in two stages, in this order for a reason: tsdown bundles the components, then the CSS build scans the built dist/*.js for class strings and compiles exactly those through Tailwind. Deriving the utility set from the build output rather than a hand-kept list is what makes it impossible to add a component whose classes have no CSS.

That script carries four fatal gates:

  • dangling — every var(--x) the token map reaches for must be defined.
  • coverage — the same class strings are re-compiled against Ekanos's full token map, and the build fails if a component has begun using a token this package doesn't ship. Without this, such a utility would just silently emit nothing.
  • lockstep — tokens.css spells its dark values out twice (a media query and an explicit selector, because CSS can't share a block between them); both must stay in agreement.
  • source parity — every value in tokens.css must equal Ekanos's @kit/ui/styles/tokens.css, and theme.css must map every color, shadow, radius and font entry of Ekanos's @theme. Coverage proves the names exist; this proves the values match. src/styles/__tests__/token-parity.test.ts runs the same check with the package tests.

pack:test packs the tarball, asserts dist-only contents, fails on any @kit/ leak, asserts 'use client' preservation, then installs the tarball into a temp project outside the workspace and typechecks (skipLibCheck: false) + esbuild-bundles a consumer importing every subpath.

It also gates the stylesheets: all four must ship, clear a size floor, still contain known utilities and both theme states, resolve through esbuild as @ekanos/ui/styles.css, and carry no icon-font data — @font-face, a webfont reference or a glyph table in dist/ fails the build, for the same licensing reason the Makerkit lineage check exists.

prepack runs the build, so a publish can never ship a stale or missing dist/.