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

@contentrain/astro-kit

v0.50.9

Published

Copy-in Astro components for Contentrain sites (shadcn model): typed props, tailwind-variants, zero JavaScript by default — with a machine-readable catalog

Readme

@contentrain/astro-kit

Copy-in Astro components for Contentrain sites, the shadcn model: a site receives the component source and owns it from then on. Nothing here is a runtime dependency of the site. The package ships the components, a machine-readable catalog, builder mapping tables and a small API to copy components into a project.

The components are made for templates/astro-starter. They read its design tokens and pass its gates: astro check with the strictest preset, knip, a single stylesheet and zero JavaScript by default.

Components

| id | What it is | JavaScript | |---|---|---| | header | Brand, navigation, optional call to action | none; renders nav | | nav | Inline menu with submenus; popover drawer or <details> on phones | none; a few lines when the menu has submenus | | footer | Brand, link columns or a link row, social links, copyright | none | | hero | Page opener: heading, text, points, actions, image | none | | card-grid | Grid of cards (features, services, team) | none | | post-card | One post in a list | none | | cta | Call-to-action band | none | | faq | Questions as <details>, optional FAQPage JSON-LD | none | | tabs | Tabbed panels; readable as headed sections without JavaScript | Zag.js | | testimonial | Quotes with author and role | none | | gallery | Image grid; a click opens a native popover or a lightbox viewer | none; the lightbox option adds a few lines, no dependency | | slider | Carousel on a scroll-snap row: image, video (a file played only on request, or a YouTube/Vimeo link loaded only on click) or text-card slides | Embla | | contact-form | Contentrain Studio form in a section | Studio runtime | | newsletter | Sign-up posting straight to the list provider (Mailchimp, Kit, MailerLite, Brevo, Buttondown) | none | | embed | Video, map or player loaded on click; a provider link until then (YouTube via nocookie) | a few lines, no dependency | | pagination | WordPress-style /page/N/ links | none | | breadcrumb | Ancestors, then the current page, optional JSON-LD | none | | band | The styled fallback section: heading, lead, image, a grid of items (figure, title, detail, text, points, link) and buttons, in the measured frame | none | | dialog | A popup the visitor opens: a button and a modal <dialog> with text, buttons and a form slot; never opens by itself | a few lines, no dependency | | behavior | A behavior island: a source widget no component carries (slider, tabs, accordion, counter, toggle, filter, in-page dialog, anchor links, image lightbox, a video player kept as a poster link that plays in place on click), kept as its own markup and wired by one vanilla module; static and readable without it | _shared/behaviors.ts, no dependency | | announcement | A one-line notice above the header with an optional link; closed through a <details> | none | | consent | Cookie consent bar with accept and decline; consent is denied until the visitor accepts | a few lines, no dependency | | contact-details | Email, phone, postal address, opening hours and a map link, without a form | none | | feature-list | Short features, each a title and a line of text with an icon or a check | none | | language-switcher | Links to the same page in the site's other languages, as a list or a <details> menu | none | | logo-cloud | Client or partner logos as a grid, a row or an endless strip | none | | post-list | A list of posts as cards: a grid, stacked rows, a featured first post or a compact list | none | | pricing | Price plans side by side, with a featured plan | none | | prose | A rich-text body as the editor wrote it, styled by Tailwind Typography; the fallback for unmapped elements | none | | search-box | A search field that sends the visitor to /search?q=… | none | | social-links | Links to the site's profiles on other networks | none | | split | An image beside a block of text and buttons | none | | stats | A row of figures with a label each; optionally counts up once in view | a few lines for the count-up, no dependency | | steps | An ordered sequence of steps, as a list or a row | none | | team | Portrait, name, role and bio of each person | none |

Each component lives in components/<id>/:

  • <Name>.astro: the component. Variants use tailwind-variants, props are typed, and every optional prop also accepts undefined.
  • meta.json: its catalog entry. Props are Contentrain FieldDefs with a description; the entry also lists variants, slots, JavaScript, npm dependencies, sources (the builder elements it replaces) and accessibility notes.
  • fixtures.json: the visual-regression cases. Every variant option is covered.

components/_shared/ holds Section, Button, KitImage, measured.ts and the value types (ImageInput, LinkInput, ActionInput, NavItem, MeasuredStyle).

Measured style

A migration can hand a section the style it measured on the source page: measured: MeasuredStyle (the same fields as MeasuredStyle in @contentrain/types): the frame's width (containerPx), padding (padY, padX), the background (bg.color, bg.image with overlay, size, position), item columns at 390, 768 and 1280 px (the kit's base, md and lg; a missing 768 takes the smaller of 2 and the 1280 value, else the 390 value; a missing 1280 takes the 768 value, else the 390 value; a missing 390 is 1), the item gap, the text tone (dark = light text on a dark band; without it a fill's own lightness decides), where the heading and lead sit (align: start, center, end), the card radius and a minHeight.

Absent, a section takes the site's tokens as before. Present, it wins over the tone and columns variants. Section turns it into inline custom properties (--kit-cols-390/768/1280, --kit-gap, --radius-card, --container-page, --spacing-gutter) and the section's own padding, height and fill; a component's item grid reads them through measuredGrid(measured). Every value is checked first (components/_shared/measured.ts): a number out of range, a colour that is not hex, rgb(a), hsl(a), transparent, white or black (an overlay may also be a linear or radial gradient of such colours), an image address that is not http(s) or root-relative, or a position outside its keywords is dropped, never written into a style attribute.

Tokens

Components use colour roles and widths, never raw values. A site defines them in @theme (the starter already does): --color-surface, --color-surface-muted, --color-ink, --color-ink-muted, --color-line, --color-accent, --color-accent-ink, --color-accent-text, --color-accent-text-inverse, --font-sans, --container-prose, --container-page, --container-wide and --radius-card.

Text in the accent (an eyebrow, a category link) reads --color-accent-text, never --color-accent: an accent picked for fills can be too light for text on the page. The starter sets it to the accent; a site whose accent does not reach 4.5:1 on its surface sets a readable one. A dark section (tone="inverse") reads --color-accent-text-inverse in its place.

Quieter text (an intro, a caption, a role, a footer link) is a colour role, never opacity: dimmed text lays its colour over the band, and on a mid-tone band that falls under 4.5:1. It reads --color-ink-muted on a light band, --color-ink-muted-inverse on a dark one (the starter mixes the surface toward the ink; without it, the band's own text colour) and the fill's own ink on an accent or brand band. A light surface inside a band (a card, a form panel, a menu) is back on --color-ink-muted. The classes are in _shared/muted.ts.

Section hooks

Every section component renders its root with an id and data-kit-section="<component id>" (hero, card-grid, slider, …), so a site's theme or a design layer can style one kind of section, or one section, without touching the component. The id is the one the page gives, or the component id counted per page (hero, then hero-2), the same on every build.

A migrated site also carries its theme's type and shape, and components read these with a fallback to the kit's own values, so a site without them looks unchanged:

  • --font-weight-heading: section headings fall back to 700, item titles to 600.
  • --font-weight-body, --text-body, --leading-body and --leading-heading: the body and rich-text type. These are in the starter's CSS.
  • --radius-control: buttons and form controls.
  • --radius-image: images, which fall back to --radius-card.
  • --spacing-gutter: the page frame's side padding, which falls back to 1rem on phones and 2rem from md up.

The theme's type scale and rhythm are not read by components. The site's theme applies them to the kit's markers, and only when the site sets them:

  • --text-heading-1 to --text-heading-3: headings inside main.
  • --text-body: [data-kit-text], the lead and body paragraphs of the section components.
  • --text-nav: the header navigation, [data-cr-part="nav-header"].
  • --text-footer: the footer's text and links, [data-kit-footer] (the kit's own size is text-sm).
  • --spacing-section: the distance between two consecutive sections; each [data-kit-section] gets half of it as vertical padding, except where data-kit-spacing="none".
  • data-kit-brand (the site title in Header and Footer) and data-kit-tagline (Footer) take the theme's own sizes: the title from where it sits (body in the header, a heading in the footer), the tagline from its preset.

The full list is in catalog.json under tokens.

API

pnpm add -D @contentrain/astro-kit
import { copyComponents, KIT_COMPONENTS_DIR, loadCatalog, loadMapping, planCopy, rulesFor } from '@contentrain/astro-kit'

const catalog = await loadCatalog()
const plan = planCopy(catalog, ['header', 'card-grid']) // adds the components they render (nav, search-box), the shared files and the npm packages needed
await copyComponents(plan, KIT_COMPONENTS_DIR, '/path/to/site') // → src/components/kit/<id>/…
// install plan.dependencies in the site

const gutenberg = await loadMapping('gutenberg')
rulesFor(gutenberg, 'core/cover') // → the rule that turns a cover block into a hero, with its prop sources

validateCatalog, componentsForSource, validateMapping and unmappedSources check the catalog and the mapping tables against each other.

Mapping tables

mapping/<builder>.json (gutenberg, elementor, divi, classic) map builder elements to components, with a source for each prop from a small closed set of expressions (see src/mapping.ts). They are versioned, and an element no rule covers stays rich text (fallback: "prose"). When one rule matches an element, the outermost match owns its subtree. defaults lists attribute values a builder leaves out of its data while they are at their default (Elementor saves no video_type for a YouTube video); when.attr and attr: read through them (attrsOf).

Development

pnpm --filter @contentrain/astro-kit catalog   # regenerate catalog.json from meta.json files
pnpm --filter @contentrain/astro-kit test      # catalog, mapping and starter-copy checks
node packages/astro-kit/scripts/visual.mjs --local-sdk [--only <id>] [--update]

visual.mjs copies the starter and installs every component into it. It renders each fixture at /kit/<id>/, type-checking the fixtures against the component props, then runs astro check and a build. Last, it compares screenshots at 390, 768 and 1280 px with visual/<platform>/. The starter carries some kit components in src/components/kit/, and a test keeps those copies identical to the source here.

Part of the Contentrain ecosystem

Contentrain is a content governance ecosystem for teams and AI agents; agents work on the content through the MCP server. This package is one of the open-source tools (docs) and a building block for moving WordPress sites to Astro, which Migrate does as a paid service. Studio is the team surface for review and roles (and runs the forms behind contact-form); Bridge is a free WordPress plugin that exports a site as a Contentrain store.

License

MIT