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

stackable-components

v1.4.2

Published

Reusable React components for Contentstack projects

Readme

Stackable Components

Ready-made React section components for Contentstack projects — heroes, banners, grids, pricing tables, FAQs, footers and more. Drop them onto a page, feed them entry data, and they render.

Installation

npm install stackable-components

Install the peer dependencies too, if your project doesn't already have them:

npm install react react-dom @heroicons/react react-icons

Quick start

import { HeroBanner, ProductCard } from 'stackable-components';
import 'stackable-components/dist/styles.css';   // required

export default function MyPage() {
  return (
    <>
      <HeroBanner
        heading="Welcome to Our Site"
        subheading="Discover amazing products"
        cta_button={{ text: 'Get Started', link: '/products' }}
      />
      <ProductCard
        product_name="Amazing Product"
        description="This product will change your life"
        price={99.99}
      />
    </>
  );
}

You don't need Tailwind. dist/styles.css is a self-contained build that carries every utility the components use. If your project already runs Tailwind, importing it alongside is fine.

Every prop is optional and has a sensible default, so a component renders something reasonable before any content reaches it.

Runtime renderer

A site doesn't have to import components one by one. StackablesEntry renders any entry straight from its content type's schema, so one route can serve every page of a Stackables-published stack:

import { StackablesEntry, parseSiteConfig, pageConfigFor, referencePaths } from 'stackable-components';

// entry:        the fetched entry (references resolved — see referencePaths(schema))
// schema:       the entry's content type schema, with global field schemas included
// config:       the `stackables_site_config` entry Stackables publishes into the stack
<StackablesEntry entry={entry} schema={contentType.schema} config={config} page={pageConfigFor(config, contentType.uid)} />

A global_field field renders as one component, a reference field as one per referenced entry, a group as a run of sibling sections and a blocks field as one component per block, in schema order. Asset objects are flattened to their URLs. Which component a content type / global field uid maps to comes from the site config's components map (falling back to the uid in PascalCase); per-section widths and the page's container cap come from its pages entry. StackablesChrome renders the site header / footer the same way from the config's header / footer, and readTypography + themeFromTypography turn a composed Typography component into a ThemeProvider theme.

A site can supply its own components: components (by library name) replaces a library component wherever it appears; componentsByUid (by content type / global field uid) renders every field pointing at that uid — including uids the library has no component for — and, given the entry's own contentType, can take a whole page type over. renderUnknown is called for anything nothing renders.

Custom pages (1.2.1)

A page type made directly in Contentstack — one Stackables never designed, so the site config says nothing about it — routes and renders too:

  • buildRoutes(config, contentTypes) gives every content type with URLs a route after the site config's pages, from its URL Prefix joined to its URL Pattern (contentTypeUrlPattern: prefix /newpage/ + pattern /:title → /newpage/:title; {title} is accepted as a placeholder too). Such routes have custom: true and page: null. Where one shares a URL shape with a designed page, the designed page is tried first.
  • StackablesEntry renders a custom page's component fields as usual, and its plain fields — text, rich text (HTML or JSON), Markdown, files, links, numbers, dates, booleans — as a document: consecutive plain fields share one section in the container, styled by the .sk-plain rules in styles.css. A custom page with no component fields also gets its title as an <h1>. This is renderPlainFields, which defaults to on exactly when no page is given; a page designed in Stackables renders precisely as before.

Markdown fields are parsed by the library itself (renderer/markdown.tsx, no dependency) straight to React elements — CommonMark's everyday set plus GitHub's tables, task lists, strikethrough and bare links; raw HTML in the text is shown as text, never interpreted. A boolean shows with its label ("Gift wrapping: Yes" / "No"; .sk-plain__boolean), and nothing when the entry never set it.

A multi-entry reference field made in Contentstack (field_metadata.ref_multiple, with multiple left false) renders every entry it holds, not only the first — isMultipleReference(field) is the test, exported alongside the others.

Field overrides — StackablesEntry's fields prop replaces the rendering of any plain field, the kinds the library never renders (custom, json, taxonomy) included:

<StackablesEntry
  fields={{
    uids: { care_notes: CareCard, "details.size_chart": SizeChart, internal_notes: null },
    customFields: { blt0123456789abcdef: StarRating }, // by custom field extension uid
    types: { markdown: ArticleMarkdown, boolean: YesNoBadge },
  }}
  /* … */
/>

Checked most specific first: uids[path], uids[uid], customFields[extension uid], types[kind]; null hides a field; uids.title replaces a custom page's title heading. types apply where plain fields render (a custom page); uids and customFields on every page. An override receives { field, value, kind, path, entry, parent, editTag, children }, children being the library's own rendering. Kinds: text, multiline, markdown, html, json_rte, number, date, file, link, boolean, select, custom, json, taxonomy (fieldKind(field) tells which). A select shows its choice's label; custom fields, plain JSON and taxonomies render only through an override.

PlainField, isPlainField and hasComponentFields are exported for a site that takes a custom page over but wants its text rendered the same way. Add .sk-plain to an element for the typography alone; the automatic sections also carry .sk-plain--section for their padding.

Every content component is reachable by name through the registry (getComponent('HeroBanner')) — withSectionWidth registers each export under the display name it is given, which is why that name must match the catalog's react_component.

Server code (a Next.js generateMetadata, say) should import the pure helpers from stackable-components/runtime instead — same functions, no components in the bundle, so it loads inside a React Server Component where the main entry cannot.

Available Components

Typography

  • Typography: Themed text primitive for headings/body copy

Hero Banners

  • HeroBanner: Full-width gradient hero with CTA button
  • SplitHeroBanner: Split layout hero with content and image placeholder
  • HeroBannerClassic: Classic hero with heading lines, avatars, and footer stats
  • SpotlightHero: Hero with a spotlighted CTA list

Banners

  • Banner / BannerLight: Full-width promo banners (dark/light)
  • BannerCallout: Inline callout banner
  • PromoBanner: Rotating multi-slide promo banner
  • CommunityBanner: Banner with avatar stack and CTA
  • CountdownBanner: Banner with a countdown timer
  • CookieConsentBanner: Cookie consent bar

Carousels & Sliders

  • ImageVideoCarousel: Interactive image/video carousel with navigation
  • CircleImageCarousel: Circular image carousel
  • VerticalProductCarousel: Vertical scrolling product carousel
  • GalleryCarousel: Gallery-style slide carousel
  • ProductSlider: Product slide carousel with CTA

Product Components

  • ProductCard / ProductCardRegular / ProductCardLarge: Product display cards
  • ProductBanner: Banner-style product highlight
  • ProductListing: Grid layout for multiple products (PLP)
  • ProductDetails: Product detail page layout (PDP)

Cards

  • FeatureCard: Feature highlight card with icon
  • ResourceCard: Resource/article card list
  • ProfileCard: Profile card with stats
  • FlipCard: Card with a flip animation
  • CardAccordion: Expandable accordion card

Grids

  • BentoGrid: Bento-style mixed grid layout
  • IndexSection: Indexed list/grid section
  • HexTileGrid: Hexagonal tile grid
  • CategoryGrid: Category tile grid
  • FlipCardGrid: Grid of flip cards
  • ImageGrid / ImageGridDynamic: Static and dynamic image grids

Testimonials & Reviews

  • Testimonial: Testimonial quote block
  • RatingSummary: Aggregate rating summary
  • CardStack: Stacked testimonial cards
  • Reviews: Review list section

Logo Cloud

  • LogoCloud: Scrolling/static logo strip

FAQ

  • FAQ: Standard FAQ accordion
  • FaqCategorized: FAQ grouped by category

Empty State

  • EmptyState: Empty/placeholder state block

Pricing

  • Pricing: Pricing tier comparison
  • PricingSpreadCard: Single spread pricing card

Statistics

  • StatCounterRow: Row of animated stat counters

Forms

  • ContactForm: Customizable contact form
  • ContactFormSplit: Split-layout contact form
  • NewsletterSignup: Newsletter signup form

Navigation

  • NavigationBar: Responsive navigation with mobile menu
  • ExtendedNavigation: Multi-level navigation menu
  • BreadcrumbNavigation: Breadcrumb trail
  • Sidebar: Sidebar navigation
  • FloatingDock: Floating dock/quick-links nav

Footers

  • FooterSimple: Minimal single-row footer
  • FooterMultiColumn: Multi-column footer with links

CTAs

  • TextCTA: Text-led call-to-action section
  • JustifiedTextCTA: Justified-text call-to-action section

Buttons

  • PrimaryButton: Primary action button
  • ToggleSwitch: On/off toggle control

Article Pages

  • ArticleDetails: Article detail page layout
  • ArticleListing: Article listing page layout

Tabs

  • Tabs: Tabbed content section

Section Introduction

  • SectionIntroduction: Section heading/intro block
  • SectionIntroductionTwoColumn: Two-column section intro

Video

  • VideoSection: Heading block with a video that plays in place — a video file or a YouTube, Vimeo or Loom link

Feature Benefits

  • FeatureBenefitStrip: Compact benefit strip
  • FeatureBenefit / FeatureBenefitDark: Benefit list sections (light/dark)
  • FeatureBenefitWithImage: Benefit section with supporting image
  • FeatureBenefitRenderer: Variant-driven benefit renderer
  • StandardBenefit: Configurable standard benefits section

Feature Showcase

  • StickyScrollFeature: Sticky scroll-driven feature showcase

Timeline

  • VerticalTimeline / TimelineHorizontal / TimelineColumn: Timeline layouts
  • TimelineYearBlocks: Year-grouped timeline blocks

Feature Comparison

  • FeatureComparisonTable: Tabular feature comparison
  • FeatureComparisonVs: Head-to-head comparison
  • FeatureComparisonCards: Card-based comparison

Team

  • TeamBanner: Team member showcase banner

Social

  • SocialPostEmbed: Embedded social post
  • SocialLinks: Social media link list

Process

  • ProcessStep: Step-by-step process section

Awards

  • AwardsSection: Awards/recognition showcase

Auth

  • AuthSplitScreen: Split-screen auth (sign in/up) layout

Page width

Components sit inside a page container that the page owns, not the component. Each section picks one of three modes via container_width:

| mode | section box | content inside | |---|---|---| | inherit (default) | stops at the container | stops at the container | | full_background | full page width — the background bleeds edge to edge | stops at the container | | full | full page width | full page width |

Set the container once for the page with PageLayout:

import { PageLayout, HeroBanner, FAQ, StatCounterRow } from 'stackable-components';

<PageLayout maxWidth={1280}>
  <HeroBanner    container_width="full"            {...heroProps} />
  <FAQ            container_width="full_background" {...faqProps} />
  <StatCounterRow container_width="inherit"        {...statsProps} />
</PageLayout>

Or skip PageLayout and pass container_cap per component:

<FAQ container_width="full_background" container_cap={1280} {...props} />

container_cap accepts a number (px), a CSS length ('80rem'), or 'full' for no cap. The container width is always a ceiling — it can pull content in, never push it out, so a 1280px container never overflows a 390px phone. Default is 1280px.

Add this to your global stylesheet so no section can ever widen the page:

html, body { max-width: 100%; overflow-x: hidden; }

Theming

Brand colours, fonts and button styling come from CSS variables. Set them with ThemeProvider:

import { ThemeProvider } from 'stackable-components';

<ThemeProvider theme={{ primary: '#e11d48', containerWidth: '1280px', fontFamily: 'Inter, sans-serif' }}>
  {children}
</ThemeProvider>

Or set the variables yourself in CSS — --stackables-primary, --stackables-container-width, --stackables-font-family, and the --stackables-btn-* family.

Contentstack Live Preview

Every component accepts a $ prop carrying Contentstack's edit tags, one per editable field, so entries stay click-to-edit in Live Preview:

<FAQ heading={entry.heading} faqs={entry.faqs} $={entry.$} />

TypeScript

Full type definitions ship with the package:

import type { HeroBannerProps, SectionWidth, StackablesTheme } from 'stackable-components';

Each component exports its own props interface (FaqProps, PricingProps, …), and every component additionally accepts container_width and container_cap.

License

MIT — a Contentstack Solutions project.