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

@aimee.shop/blocks

v0.1.1

Published

React component library for rendering Aimee CMS block content on storefronts

Readme

@aimee.shop/blocks

React component library for rendering Aimee CMS block content on storefronts.

This package ships 24 read-only block renderers (including composition types). It is a convenience layer over raw SDK data -- storefronts can also render blocks manually.

Install

npm install @aimee.shop/blocks

Peer dependencies: react >= 18, react-dom >= 18. @aimee.shop/sdk is an optional peer dependency -- this package never imports it; all data arrives via props.

Catalog

A Storybook catalog (dev-only, not published) renders every BLOCK_DEFS type through BlockRenderer with fixture content:

npm run storybook
npm run build-storybook

Theme tokens (--aimee-heading-color, --aimee-cta-bg, --aimee-font-family) are toolbar globals. Viewports: mobile 375 and desktop 1280. The catalog does not call a live assistant.

Usage

Import the stylesheet once (for example in a Next.js root layout):

import '@aimee.shop/blocks/styles.css';

Fetch content with the SDK and pass blocks to the renderer:

import { BlockRenderer } from '@aimee.shop/blocks';

// In an async Server Component
const entry = await sdk.cms.public.getContent('blog', slug);

return <BlockRenderer blocks={entry.blocks} />;

Block data shape (mirrors the CMS API):

interface Block {
  id: string;
  type: string; // one of 24 known types; unknown types render as JSON via CustomBlock
  sortOrder: number;
  content: Record<string, unknown>;
  settings?: Record<string, unknown>;
}

Block types

text, heading, image, gallery, video, embed, cta, product_card, product_grid, quote, code, divider, spacer, accordion, tabs, custom, form_embed, hero, promo_banner, card_grid, comparison_table, video_lightbox, columns, container, chat_widget.

Content for each type is validated at render time by exported parsers (parseHeadingContent, parseGalleryContent, ...). Invalid or empty content renders nothing; recoverable fields get defaults (heading level 2, grid columns 3, etc.). Unknown block types fall back to CustomBlock, which renders the raw content as JSON.

Composition (columns / container)

columns and container hold nested Block[] inside their content JSON. BlockRenderer recurses into children (same parse/dispatch path, products and overrides flow down). Limits:

  • max nesting depth 3 (MAX_BLOCK_DEPTH); deeper composition blocks degrade to CustomBlock
  • only columns / container hold children
  • columns.length capped at 6
  • column width is 'auto' or a positive relative weight (CSS fr)

Notes on specific types

  • hero: headline (h1 by default, demotable to h2 via headlineLevel), optional subheadline, background image and/or video (video wins, image becomes the poster and the prefers-reduced-motion fallback), contrast overlay (overlayOpacity, 0-1), up to 2 CTAs, height small|medium|large|full.
  • promo_banner: text with optional link. First block type with typed settings: dismissible, startsAt/endsAt (ISO 8601 schedule window), storageKey (localStorage key for dismissal; falls back to the block id). Pages cached by ISR/static rendering re-evaluate the window client-side at hydration, but keep revalidation intervals shorter than your window edges.
  • card_grid: cards with title, plain-text body, image/icon, optional link (single anchor, CSS-expanded hit area). Optional linkText makes that anchor a visible underlined CTA under the body; optional showTitle: false hides the heading (title stays required and is the image alt fallback).
  • comparison_table: semantic table with column/row headers, optional caption and highlightColumn (0-based), horizontal scroll wrapper that is keyboard focusable.
  • video: YouTube and Vimeo page URLs are rewritten to their embed form. Allowlisted hosted players (videos.sproutvideo.com) and provider: 'hosted' pass the http(s) URL through as the iframe src. Unrecognized URLs render a visible error and console.warn -- never a silent link. Raw media files are not hosted players; use hero background video or embed for those.
  • video_lightbox: thumbnail button opening a modal embed (same URL rules as video) with autoplay. Unrecognized URLs keep the thumbnail and show the same visible error.
  • columns: multi-column layout; each column has width and nested blocks.
  • container: grouped section with nested blocks and optional style default|emphasis|accent. Presentation fields (all optional): backgroundColor / textColor (#RGB or #RRGGBB), backgroundImage / mobileSrc (http(s), mobileSrc via <picture>), paddingY none|sm|md|lg, minHeight none|md|lg. Typed settings: align start|center|end, width contained|full, cssClass, htmlId. width=full bleeds the outer surface and constrains .aimee-container__inner.

Block settings

settings mirrors content: raw Record<string, unknown> narrowed by runtime parsers, never cast. Block types with typed settings appear in BlockSettingsMap (promo_banner -> PromoBannerBlockSettings, container -> ContainerBlockSettings); for every other type the raw settings record passes through unchanged. Settings may carry extra keys (for example admin variant/personalization metadata); parsers read what they know and ignore the rest. Components rendered through BlockRenderer also receive blockId -- the CMS block id -- for per-block client state such as promo banner dismissal.

Server and client components

Components are React Server Component compatible by default. Exactly six blocks are client components ('use client'): AccordionBlock, TabsBlock, GalleryBlock, PromoBannerBlock, VideoLightboxBlock, ChatWidgetBlock. Composition blocks (columns, container) are server components. No renderer is async and none fetches data -- everything renders from props.

Product blocks and the products map

product_card and product_grid content contains only product IDs. The CMS API does not resolve products; the storefront fetches them and passes a map:

const products = {
  [productId]: { id, name, slug, price, compareAtPrice, currency, images },
};

<BlockRenderer blocks={entry.blocks} products={products} />;

When a product is missing from the map (or no map is passed), the card renders a visible "Product unavailable" placeholder instead of disappearing silently.

Chat widget and the assistant session

chat_widget renders the shopper assistant (ai-toolkit POST /v1/ai/shopper/chat) as a floating drawer (mode: "drawer", launcher button bottom-right or settings.position: "left") or an inline panel (mode: "inline"). Content: title, greeting, placeholder, launcherLabel, suggestions (max 4 quick prompts). Settings: position, pageType.

The session is storefront data, not CMS content, so it is passed to BlockRenderer as assistant (the same way products is):

<BlockRenderer
  blocks={entry.blocks}
  assistant={{
    endpoint: 'https://ai-dev.aimee.shop',
    publishableKey: process.env.NEXT_PUBLIC_AIMEE_PUBLISHABLE_KEY,
    customerToken: session?.accessToken, // sent as X-Customer-Token
    cartId: cart?.id,
    page: { pageType: 'product', productId: product.id },
    onCartUpdate: (cart) => adoptCart(cart.cartId),
    onCheckout: (url) => router.push(url), // default: window.location.assign(url)
  }}
/>

The widget makes no SDK calls. Every commerce action goes through the assistant; the storefront only bootstraps the session (key, cart id, customer token) and adopts the cart id announced on onCartUpdate (a guest cart is created server-side on the first write). Without assistant, an inline block shows "Chat is unavailable" and a drawer renders nothing.

Stream payloads render as cards: product search results and details, the cart (cart_update also replaces the running cart), orders, and the checkout_handoff link to the hosted checkout URL. Gate refusals (status: "held"), tool errors, and warning events show as a muted notice line. The assistant only ever streams text and these events; the widget round-trips the text history plus conversationId (X-Session-Id).

For a custom UI, streamShopperChat, parseSseChunk, readShopperEvent, shopperChatReducer, and the card components are exported.

Overriding block components

import type { BlockComponentProps, HeadingBlockContent } from '@aimee.shop/blocks';

function MyHeading({ content }: BlockComponentProps<HeadingBlockContent>) {
  return <h2 className="my-heading">{content.text}</h2>;
}

<BlockRenderer blocks={blocks} overrides={{ heading: MyHeading }} />;

Overrides are typed per block type: the heading override receives parsed HeadingBlockContent, not raw JSON.

Theming

All styling uses CSS custom properties with the --aimee- prefix. Package defaults follow Aimee (Blue CTA #0066F7, Ink headings #050A1C). Load Quicksand yourself if you want the heading stack to render; this package only sets the family name. Merchant themeSettings overrides stay the same. Defaults ship inside @layer aimee-blocks, so unlayered rules in your stylesheet always win -- no :root:root or !important needed:

:root {
  --aimee-font-family: 'Inter', sans-serif;
  --aimee-font-heading: 'Playfair Display', serif;
  --aimee-heading-color: #111;
  --aimee-cta-bg: #0066f7;
  --aimee-cta-color: #fff;
  --aimee-spacing-md: 2rem;
  --aimee-border-radius: 0.25rem;
  /* Gradient text: set BOTH tokens or text becomes invisible */
  --aimee-headline-gradient: linear-gradient(90deg, #0066f7, #fb74c6);
  --aimee-headline-text-fill: transparent;
}

Full token list:

  • Typography: --aimee-font-family, --aimee-font-heading, --aimee-font-size-base, --aimee-line-height-base
  • Headline gradient: --aimee-headline-gradient, --aimee-headline-text-fill (pair; default no-op)
  • Colors: --aimee-heading-color, --aimee-text-color, --aimee-link-color, --aimee-border-color, --aimee-bg-muted
  • CTA: --aimee-cta-bg, --aimee-cta-color, --aimee-cta-hover-bg, --aimee-cta-hover-color, --aimee-cta-border-color, --aimee-cta-radius, --aimee-cta-padding, --aimee-cta-font-weight, --aimee-cta-text-transform
  • Hero CTA (defaults to matching --aimee-cta-*): --aimee-hero-cta-bg, --aimee-hero-cta-color, --aimee-hero-cta-hover-bg, --aimee-hero-cta-hover-color, --aimee-hero-cta-border-color, --aimee-hero-cta-radius, --aimee-hero-cta-padding, --aimee-hero-cta-font-weight, --aimee-hero-cta-text-transform
  • Spacing: --aimee-spacing-xs, --aimee-spacing-sm, --aimee-spacing-md, --aimee-spacing-lg, --aimee-spacing-xl
  • Layout: --aimee-max-width, --aimee-border-radius
  • Code: --aimee-code-bg, --aimee-code-border, --aimee-code-font
  • Quote: --aimee-quote-border-color, --aimee-quote-bg
  • Overlay: --aimee-overlay-bg
  • Product card: --aimee-product-card-shadow, --aimee-product-card-hover-shadow
  • Hero: --aimee-hero-min-height-small|medium|large, --aimee-hero-overlay-color, --aimee-hero-text-color, --aimee-hero-bg
  • Promo banner: --aimee-promo-banner-bg, --aimee-promo-banner-color
  • Card grid: --aimee-card-grid-bg, --aimee-card-grid-shadow, --aimee-card-grid-hover-shadow, --aimee-card-grid-title-size (1.125rem), --aimee-card-grid-body-size (0.9375rem)
  • Comparison table: --aimee-table-header-bg, --aimee-table-highlight-bg
  • Video lightbox: --aimee-play-button-bg, --aimee-play-button-color

Hero content also supports optional eyebrow (plain text above the headline).

All classes are BEM with the aimee- namespace (.aimee-heading, .aimee-product-card__price).

Security

HTML content (TextBlock, EmbedBlock) is sanitized with sanitize-html before rendering (CJS-safe on Vercel Node; no jsdom). EmbedBlock allows iframes through sanitization; VideoBlock, FormEmbedBlock, VideoLightboxBlock, and hero background media only render http(s) URLs. Card grid, comparison table, hero, and promo banner content is plain text -- no new HTML sanitization surface.