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

@hanzo/docs-ui

v17.0.5

Published

The user interface of @hanzo/docs — layouts, sidebar, table of contents, search, Ask AI and the MDX components — composed from @hanzo/ui and @hanzo/gui, styled by @hanzo/design.

Readme

@hanzo/docs-ui

The user interface of @hanzo/docs: layouts, sidebar, table of contents, search, Ask AI, and the components MDX renders. Composed from @hanzo/ui and @hanzo/gui, coloured by @hanzo/design. Nothing else: no Tailwind, no Radix, no Base UI, no shadcn, no cn, no class strings, no generated CSS.

This file is the contract. Read it before writing a component.

What sits under what

@hanzo/design            CSS custom properties: --background --foreground --border
   ↑ read by             --muted-foreground --card --primary --ring --text-* --font-*
@hanzo/ui/gui-config     the gui config whose theme keys resolve to those variables
   ↑ mounted by          ($background, $color, $borderColor, $color4, $placeholderColor …)
@hanzo/ui                gui-backend components: Button, Accordion, Collapsible, Tabs,
                         Dialog, Popover, Tooltip, ScrollArea, Command, Input, Separator …
   ↑ composed by
@hanzo/docs-ui  (this)   DocsLayout, HomeLayout, Sidebar, TOC, SearchDialog, AskAI,
                         Callout, Card, CodeBlock, Steps, Tabs, TypeTable, Files, …
   ↑ imported by
a docs site              docs.hanzo.ai, docs.lux.network, docs.zoo.ngo, …

@hanzo/docs-core (page tree, source loader, search clients, TOC anchors, the framework abstraction) is untouched by this package and stays UI-free.

Rules

  1. Primitives come from @hanzo/ui first, @hanzo/gui second, nothing third. A component that exists in @hanzo/ui (Accordion, Collapsible, Tabs, Popover, Dialog, Tooltip, ScrollArea, Command, Button, Input, Separator, Select …) is used from there. Layout and text come from @hanzo/gui: YStack, XStack, View, Text, SizableText, Paragraph, H1H6, Anchor, Sheet, Separator, useMedia, styled, Theme. Semantic elements come from @hanzo/gui too: Nav, Aside, Main, Article, Header, Footer, Section. Icons come from @hanzogui/lucide-icons-2. Never import react-native, tamagui, @base-ui/*, @radix-ui/*, class-variance-authority, tailwind-merge, clsx.

  2. Style is props and tokens, never class strings. Write <YStack bg="$background" borderColor="$borderColor" rounded="$4" p="$3">. The token vocabulary is @hanzo/ui/gui-config's:

    • colour: $background, $color (foreground), $color12, $color11 (secondary text), $color10, $placeholderColor (tertiary), $borderColor, $color4 (edge), $color3 (raised surface), $color2, $accentBackground, $accentColor, $outlineColor (focus ring — the ONE colour allowed to be loud)
    • space: $1$20 on the 4px ramp ($1=4 $2=8 $3=12 $4=16 $5=20 $6=24 $8=32 $10=48 …); negative mirrors exist
    • radius: $2=6 $4=8 $6=12 $true=8 $10=pill
    • text: size="$1"=11 $2=13 $3=14 $4=15 $6=17 $7=21 $8=26 $10=32
    • font: fontFamily="$mono" for code; body is $body Where design's own vocabulary is needed for something gui has no key for (a hairline, an error tint, an elevation), reach for the CSS variable by name: borderColor="var(--border-hairline)", bg="var(--state-error-bg)". Never a hex, never an rgb. Colour is monochrome; only live/error/warning/success states carry hue, and those come from design's --state-* variables.
  3. className stays on every public component and is passed through. It is the escape hatch a host uses to reach a component with its own CSS. We never read it, never merge it, never generate one.

  4. 'use client' on every file that renders a gui component. @hanzo/gui ships no client directives, so the boundary is ours. Server-safe files — layouts/*/index.tsx (they compose client parts and pass JSX), mdx.server.tsx, og.tsx, i18n.tsx, components/sidebar/tabs/index.tsx, layouts/shared/index.tsx (types + resolveLinkItems), utils/urls.ts, utils/merge-refs.ts — carry no directive and import no gui. Anything a server layout hands to a client component must be serialisable: JSX, strings, numbers, plain objects, arrays. Never a function across the boundary.

  5. Layout is stacks and media props, not a CSS grid. The docs shell is a row: Aside (sidebar) · Main (page) · Aside (toc). Widths are the constants in src/theme.ts (SIDEBAR_WIDTH, TOC_WIDTH, HEADER_HEIGHT, LAYOUT_WIDTH) and they are also written to the DOM as --fd-sidebar-width, --fd-toc-width, --fd-header-height, --fd-layout-width on the layout root, so a host's own CSS can still read them. The document scrolls, not a box inside it: the layout root carries a minH and never a height, the row and the page column grow with flexBasis="auto" (flex={1} on the vertical axis is a zero basis, which is a column that collapses to nothing), and only the rail and the contents column pin themselves — position: sticky with the top and height rail publishes in theme.ts, each scrolling its own overflow. Responsive behaviour is $md/$lg/$max-md props and useMedia(), never window.matchMedia in render. Below md the sidebar is a Sheet (drawer) and the TOC is a popover.

  6. The prose that MDX emits is styled by one stylesheet: css/prose.css. MDX renders raw p, ul, ol, li, blockquote, hr, strong, em, inline code, table — elements no component wraps. DocsBody renders <Article className="fd-prose"> and css/prose.css styles descendants of .fd-prose with design's variables. It is plain CSS, hand-written, ~150 lines, and it is the ONLY place a selector like .fd-prose p may appear. Shiki's token colours live in css/shiki.css. css/style.css imports both plus the design token files. Nothing generates CSS at build time in this package.

  7. The gui atomic sheet is a build artefact of the host, produced by our bin. hanzo-docs-css app/gui.css (bin/css.mjs) bundles @hanzo/ui/gui-config and this package's own dist with esbuild, renders both layouts and every component MDX can put in a page once through react-dom/server, then calls css(monochrome) and writes the file. The render is the point: gui writes the rule behind px="$4" when that component first constructs, so a sheet taken from the config alone carries the themes and almost nothing the pages wear. A component that cannot render in Node is skipped and the sheet is smaller by that component. A host runs it in prebuild and predev, imports the file once, and mounts RootProvider which renders GuiProvider with disableInjectCSS. Never getCSS(), never useServerInsertedHTML.

  8. Theme is a class on <html>, owned by next-themes, attribute="class", values dark/light. Dark is the root. RootProvider mounts next-themes, then GuiProvider reading resolvedTheme, disableRootThemeClass. ThemeToggle calls useTheme().setTheme. Nothing else touches theme.

  9. One search dialog, on @hanzo/ui's Command. SearchDialog and its parts keep their names and props from the previous version; the list is a CommandList, keyboard navigation is the Command's own. The three backends (search-default, search-orama, search-algolia) stay thin: they own the query, SearchDialog owns the rendering.

  10. Ask AI is a component of this package, brand-agnostic. AskAI in components/ai/ talks to an OpenAI-compatible endpoint (default https://api.hanzo.ai/v1/chat/completions) with the host's key, sends X-Retrieval: 1 and X-Retrieval-Store: <store> so answers are grounded in the host's index, streams SSE, renders markdown via the same MDX components. The trigger sits in the docs header and the search dialog ("Ask AI about …"). Nothing about Hanzo or Lux is hardcoded; the host passes endpoint, token, store, greeting.

  11. API compatibility with 16.x is deliberate. Every export name, prop name and subpath from the 16.x UI package that a host used still exists here, with the same meaning, except: buttonVariants (was a Tailwind class generator — hosts use Button from @hanzo/ui), cn (was twMerge), and the flux and notebook layouts (removed; layouts/docs is the docs layout). A host upgrades by bumping the version, deleting its Tailwind setup and importing @hanzo/docs-ui/css/style.css + its generated gui.css.

  12. Names. Files and exports keep the previous names (hosts import them). New things are named for what they are, one word where one will do: theme.ts, ask.tsx, transport.ts. No Wrapper, no Manager, no Helper.

The file tree

src/
  theme.ts                    layout constants + the --fd-* names they publish
  i18n.tsx                    defineI18nUI                                (server-safe)
  mdx.tsx / mdx.server.tsx    defaultMdxComponents / createRelativeLink
  og.tsx                      generateOGImage                             (server-safe)
  page.tsx                    DocsPage compat shim + re-exports
  provider/base.tsx           RootProvider: next-themes → GuiProvider → I18n → Search
  provider/{next,react-router,tanstack,waku}.tsx
  contexts/{i18n,search,tree}.tsx
  utils/{ink,merge-refs,urls,link-item,use-copy-button,use-footer-items,use-is-scroll-top}.ts(x)
  layouts/shared/index.tsx    BaseLayoutProps, NavOptions, resolveLinkItems, useLinkItems
  layouts/shared/{theme-toggle,search-toggle,language-toggle}.tsx
  layouts/docs/{index,client,sidebar}.tsx, layouts/docs/page/{index,client}.tsx
  layouts/home/{index,client,navbar}.tsx
  components/{accordion,banner,callout,card,codeblock,dynamic-codeblock,dynamic-codeblock.core,
              files,github-info,heading,image-zoom,inline-toc,steps,tabs,type-table}.tsx
  components/dialog/{search,search-default,search-orama,search-algolia}.tsx
  components/sidebar/{base,page-tree,link-item}.tsx, components/sidebar/tabs/{index,dropdown}.tsx
  components/toc/{index,default,clerk}.tsx
  components/ai/{ask,transport}.tsx
css/
  style.css   @import design tokens; @import ./prose.css; @import ./shiki.css
  prose.css   .fd-prose descendants
  shiki.css   code token colours
bin/
  css.mjs     hanzo-docs-css <out>

Reference material

  • The previous implementation (Tailwind + Base UI) was packages/base-ui; it is deleted — its names, props and behaviour live on here.
  • The idiom for gui-backend components: ~/work/hanzo/ui/pkg/ui/src/backends/gui/* (ink, slot, touch, shorthand props bg px py p m rounded items justify self gap).
  • A docs UI already written in gui primitives (sidebar, TOC with the SVG active path, code block, tabs): ~/work/hanzo/gui/apps/gui.hanzo.ai/features/docs/* and features/mdx/MDXComponents.tsx.
  • Design system rules: ~/work/hanzo/design/skills/design-system/SKILL.md.