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

poseui

v0.0.5

Published

Type-safe HTML templating engine on steroids

Readme

Pose

⚠️ Prototype — API is unstable

Type-safe HTML templating engine with a fluent Tailwind-compatible builder API. Inspired by gpui.

Zero dependencies. Fully synchronous. Emits HTML with utility class names — CSS generation is your pipeline's concern.

import pose from "poseui";
import { z } from "zod";

const button = pose
  .as("button")
  .input(
    z.object({
      variant: z.enum(["primary", "secondary"]).default("primary"),
      disabled: z.boolean().default(false),
    }),
  )
  .px(4)
  .py(2)
  .rounded()
  .font_semibold()
  .transition()
  .when("variant", {
    primary: (b) => b.bg("indigo-600").text_color("white"),
    secondary: (b) => b.bg("slate-200").text_color("slate-900"),
  })
  .when(
    ({ disabled }) => disabled,
    (b) => b.opacity(50).cursor_not_allowed(),
  )
  .child(({ variant }) => (variant === "primary" ? "Submit" : "Cancel"));

button({ variant: "primary" });
// → <button class="px-4 py-2 rounded font-semibold transition bg-indigo-600 text-white">Submit</button>

button({ variant: "secondary", disabled: true });
// → <button class="px-4 py-2 rounded font-semibold transition bg-slate-200 text-slate-900 opacity-50 cursor-not-allowed">Cancel</button>

Install

bun add poseui
bun add zod  # or valibot, arktype, any Standard Schema lib

CSS

Pose emits standard Tailwind-compatible class names and nothing else. Plug in whatever you already use:

# Tailwind v4
npx @tailwindcss/cli -i input.css -o output.css

# UnoCSS
npx unocss "**/*.ts" -o output.css

Because the HTML is plain strings, both tools pick up classes the same way they would from any other source file.

Core concepts

pose.as(tag) — start a builder for any HTML element tag.

.input(schema) — bind a Standard Schema object schema. Infers TProps from the output type so .default() transforms work. Validates on every call and throws PoseValidationError on failure.

Style methods — cover the full Tailwind utility surface: layout, spacing, typography, colour, borders, shadows, transforms, filters, animation, and more. Every method that takes a value also accepts (props: TProps) => value for dynamic styles. See the source for the complete list.

.when(pred, apply) — apply styles when a predicate returns true:

.when(({ disabled }) => disabled, (b) => b.opacity(50).cursor_not_allowed())

.when(key, cases) — switch on a prop key and apply styles per matching case. Case keys are typed to the prop's actual union — typos are compile errors:

.when("size", {
  sm: (b) => b.px(2).py(1).text_sm(),
  md: (b) => b.px(4).py(2).text_base(),
  lg: (b) => b.px(6).py(3).text_lg(),
})

Cases are Partial — unmatched values emit nothing. Multiple .when() calls stack independently and are all evaluated at render time.

.attr(name, value) — set a single HTML attribute. Value can be static or (props) => string | null. null omits the attribute entirely; "" renders it as a boolean attribute (required, disabled, etc.):

pose
  .as("a")
  .input(z.object({ url: z.string(), external: z.boolean() }))
  .attr("href", ({ url }) => url)
  .attr("target", ({ external }) => (external ? "_blank" : null))
  .attr("rel", ({ external }) => (external ? "noopener noreferrer" : null));

.attrs(record | fn) — set multiple attributes at once. Accepts a record of static/dynamic values, or a (props) => Record<string, string | null> function for when attributes depend on each other:

// record form
pose.as("input").attrs({
  type: "text",
  name: ({ field }) => field,
  required: ({ required }) => (required ? "" : null),
});

// function form
pose.as("a").attrs(({ url, external }) => ({
  href: url,
  target: external ? "_blank" : null,
  rel: external ? "noopener noreferrer" : null,
}));

.cls(value) — escape hatch for anything not covered by the builder. Accepts a raw class string or (props) => string:

pose
  .as("div")
  .cls("hover:opacity-75")
  .cls(({ active }) => (active ? "ring-2 ring-blue-500" : ""));

.child(value | fn) — append children. Accepts a string, number, another PoseElement, an array of those, or (props) => any of the above. Chainable — call it multiple times to append in order.

element(props) — render to an HTML string. Synchronous unless the bound schema uses async validation, in which case it returns Promise<string>.

Composition

PoseElement instances are valid children of other elements:

const avatar = pose.as("img").rounded_full().w(8).h(8);

const card = pose
  .as("div")
  .p(4)
  .rounded()
  .shadow_md()
  .child(avatar)
  .child(pose.as("p").text_sm().child("Hello"));

card();
// → <div class="p-4 rounded shadow-md"><img class="rounded-full w-8 h-8"></img><p class="text-sm">Hello</p></div>

Validation errors

import { PoseValidationError } from "poseui";

try {
  button({ variant: "oops" });
} catch (err) {
  if (err instanceof PoseValidationError) {
    console.log(err.issues); // StandardSchemaV1.Issue[]
  }
}

License

MIT