@ekanos/ui

中文文档
0.27.0发布于 2026/11/12MITTypeScriptES Module近 30 天下载 2794

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

原始使用文档
文档目录12 节
  1. What's exported
  2. Styling
  3. You do NOT use Tailwind — import the compiled stylesheet
  4. You DO use Tailwind v4 — import the preset
  5. What styles.css does and does not contain
  6. Page background and dark mode
  7. Tokens: generated from Ekanos's design system
  8. Fonts
  9. Icons: Phosphor, included
  10. Runtime notes
  11. Peer dependencies
  12. Building / verifying

@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:

SubpathContents
@ekanos/ui/alertAlert, AlertTitle, AlertDescription, AlertAction
@ekanos/ui/buttonButton, ButtonProps, buttonVariants
@ekanos/ui/cardCard, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, …
@ekanos/ui/badgeBadge, badgeVariants
@ekanos/ui/status-badgeStatusBadge (neutral pill + tone dot), StatusTone
@ekanos/ui/statStat, StatLabel, StatFigure, StatValue, StatChange, StatDetail
@ekanos/ui/pagePageBody, PageRail, PageTitleSection, PageToolbar, PageToolbarActions, PageRailTabs, PageRailSubTabs, PageBackButton, PageRangeSwitch, PageViewModeToggle
@ekanos/ui/dialogDialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, …
@ekanos/ui/separatorSeparator
@ekanos/ui/tooltipTooltip, TooltipTrigger, TooltipContent, TooltipProvider
@ekanos/ui/selectSelect, SelectTrigger, SelectValue, SelectContent, SelectItem, …
@ekanos/ui/formForm, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage, useFormField
@ekanos/ui/inputInput
@ekanos/ui/textareaTextarea
@ekanos/ui/switchSwitch
@ekanos/ui/dropdown-menuDropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, …
@ekanos/ui/labelLabel
@ekanos/ui/skeletonSkeleton
@ekanos/ui/iconIcon, renderIcon, resolveIcon, IconStyleOverrideProvider, name helpers
@ekanos/ui/utilscn(), isRouteActive(), checkIfRouteIsActive()
@ekanos/ui/ifIf conditional renderer
@ekanos/ui/transTrans (next-intl; needs a NextIntlClientProvider)
@ekanos/ui/spinnerSpinner
@ekanos/ui/ai-prompt-inputPromptInput compound components for AI ask surfaces
@ekanos/ui/tableTable, TableHeader, TableBody, TableRow, TableHead, TableCell, … (static tabular facts)
@ekanos/ui/data-tableDataTable, DataTableColumn and its sort/paging types (the house table: sorting, paging, row clicks)
@ekanos/ui/empty-stateEmptyState, EmptyMedia, EmptyStateHeading, EmptyStateText, EmptyStateButton (a whole empty surface)
@ekanos/ui/empty-noteEmptyNote (an empty card or section inside a populated surface)
@ekanos/ui/tabsTabs, TabsList, TabsTrigger, TabsContent
@ekanos/ui/sheetSheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, …
@ekanos/ui/confirm-dialogConfirmDialog
@ekanos/ui/search-fieldSearchField
@ekanos/ui/avatarAvatar, AvatarImage, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarBadge, PersonAvatar (picture or initials from a name), initialsFromName
@ekanos/ui/chartsSparkline, ProgressRing, BarSeries (small hand-drawn charts for widgets, coloured by the --chart-* tokens) and their geometry helpers

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:

StateResult
nothing setlight
@media (prefers-color-scheme: dark)dark
.dark or [data-theme='dark'] ancestordark, beating the media query
.light or [data-theme='light'] on :rootlight, 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, 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 text in Inter, the assistant's replies included, and headings in Urbanist at 400–500 (font-heading with font-heading-bold or font-heading-semibold), both 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=Urbanist:[email protected]&display=swap"
/>

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

Icons: Phosphor, included

Icon draws a Phosphor glyph (MIT) as an <svg> holding the icon font's character, so shadcn's [&_svg] selectors size it. The fonts ship in dist/fonts/ and styles.css / base.css declare them, so there is nothing to install. Your bundler must handle .woff2 url()s in CSS, which Next.js, Vite and webpack 5 do out of the box (esbuild needs --loader:.woff2=file). A browser downloads only the weights a page draws.

Name icons the way phosphoricons.com does: "house", "caret-down", "github-logo". A filled state glyph (a selected star) is "star:fill"; it stays filled in every style. Re-skin everything below a point to one weight:

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

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

The styles are light, regular, bold, solid (Phosphor's fill weight) and duotone. A Font Awesome name from before the move ("fa-house", "fa-brands fa-github") still draws its Phosphor equivalent, and a name that matches no icon draws shapes rather than vanishing. Check a name with isRenderableIconName().

An icon is 16px unless a size class (size-5, or a component's [&_svg] rule) or --icon-glyph-size on a container says otherwise. Like a lucide icon, Icon takes SVG props (aria-label, role, data-icon).

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/.

版本与发布记录

@ekanos/ui 共发布 35 个版本,首次发布于 2026/08/31;过去 12 个月发布了 35 个版本。

版本发布日期距今
0.27.0latest2026/11/122 天前
0.26.02026/11/112 天前
0.25.02026/11/104 天前
0.24.02026/11/077 天前
0.23.02026/11/049 天前
0.22.02026/11/0311 天前

关于 @ekanos/ui 的常见问题

@ekanos/ui 是什么?

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

如何安装 @ekanos/ui?

在项目目录运行 npm install @ekanos/ui,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^19.2.8)、next-intl(^4.14.7)、react-dom(^19.2.8)、react-hook-form(^7.68.0)、@hookform/resolvers(^5.2.2 || ^7.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@ekanos/ui 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ekanos/ui 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@ekanos/ui 可以商用吗?

@ekanos/ui 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@ekanos/ui 的下载量有多少?

根据 npm 官方下载统计,@ekanos/ui 在 2026/10/13 – 2026/11/11 这 30 天内被下载 2,794 次,日均约 93 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ekanos/ui 还在维护吗?

最新版本 0.27.0 发布于 2026/11/12(2 天前);至今共发布 35 个版本,其中过去 12 个月发布了 35 个;首次发布于 2026/08/31。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。