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

@setsquare/ui-blocks

v0.2.0

Published

A page builder for @setsquare/ui on Puck: pages made from blocks, with every task by drag, by press and by keyboard, and accessibility checks that run on the rendered page.

Readme

@setsquare/ui-blocks

A page builder for @setsquare/ui: people make web pages from blocks. Puck 0.23 holds the page data, the canvas and the undo history. Setsquare UI draws everything a person operates: the header, the block library, the outline, the settings, the block toolbar and the checks. Every task works three ways:

  • by dragging with a pointer;
  • by pressing, with no drag;
  • from the keyboard, with a screen reader.

It aims at WCAG 2.2 AAA for the editor, and at ATAG 2.0 for helping authors make accessible pages. Nothing in it has been tested with a screen reader yet, and automated checks are evidence, not conformance.

0.1.1. The first release was 0.1.0; 0.1.1 fixes a hydration error when the server and the browser are on different platforms (see the changelog). It has the editor (ContentBuilder, and each of its surfaces for a layout of your own), the blocks API (defineBlock, defineBlocks, fields), the checks (in the editor, and runChecks for the server), the help tour, sessions, and ./render for the published page. The design is in handoff/ui-blocks/spec/content-builder-spec.md, and every place this release departs from it is in PLAN.md.

It's a separate package because Puck is large. Puck 0.23's editor, with the Tiptap and dnd-kit it brings, comes to about 353 KB gzipped, more than everything @setsquare/ui exports (about 254 KB, React Aria included). Both were measured on 8 October 2026 with esbuild, minified, React left out, and gzip -9. A product that doesn't build pages shouldn't carry it.

Install

pnpm add @setsquare/ui-blocks @setsquare/ui @puckeditor/core react-aria-components

It needs @setsquare/ui 0.5 or later, Puck 0.23, React 19.2 or later, and Tailwind CSS 4 in the app. In the app's global stylesheet, after @setsquare/ui's:

@import "tailwindcss";
@import "@setsquare/ui/styles.css";
@import "@setsquare/ui-blocks/styles.css";

styles.css brings in Puck's stylesheet as a file (its no-external.css, which fetches no font from another site). Loaded that way, Puck doesn't inject its own at runtime and keep the editor hidden until it loads, so a strict Content-Security-Policy can't leave the editor invisible. It also has an @source that points Tailwind at the package's compiled components.

Puck is pinned to one minor version (~0.23.0). Its API is pre-1.0, and the builder relies on parts of it that a minor release could change. A contract test covers each of them.

Three entries

| Import | What it is | Where it runs | | --- | --- | --- | | @setsquare/ui-blocks | The editor and its parts, and the blocks API | Client components; the blocks API in server components too | | @setsquare/ui-blocks/render | Render and resolveAllData for the published page, over @puckeditor/core/rsc | Server components | | @setsquare/ui-blocks/checks | runChecks(html, { rules }), the same rules the editor runs | The browser and Node, with no React |

Blocks

import { defineBlock, defineBlocks, fields } from '@setsquare/ui-blocks';

const Hero = defineBlock({
  label: 'Hero',
  description: 'The page’s main heading, at the top.',
  category: 'text',
  evidence: { level: 'AAA', href: '/evidence/hero' },
  fields: { heading: fields.heading({ label: 'Heading', levels: [1, 2] }) },
  defaultProps: { heading: { text: 'Welcome', level: 1 } },
  render: ({ heading }) => <Heading {...heading} />,
});

export const blocks = defineBlocks({
  categories: { text: 'Text', layout: 'Layout and media' },
  blocks: { Hero, Text, Image, Columns, CallToAction },
  root: { fields: { title: fields.text({ label: 'Page title' }), language: fields.language({ label: 'Language' }) } },
  headingBlock: 'Hero',
});
  • A block is a Puck component config with a few Setsquare keys: description and icon for the library, category for its group, summary for a few words of its text in the outline, evidence for the level it's been tested at, and checks for its own rules. Anything Puck takes still works: resolveData, resolveFields, defaultProps, inline, slots.
  • fields are Puck fields that draw Setsquare UI controls in Settings, each with its label, hint and checks: text, textarea, number, select, multiSelect, radio, switch, heading (only the levels the block allows, saying which block has Heading 1), image (alt text or Decorative, through your media library), link (its words checked), richText (only the headings the block allows; pasting from Word and Google Docs keeps headings, lists and links and says what it left out), language, colour (its contrast checked), list, slot, external and custom.
  • defineBlocks groups the blocks for the library and names headingBlock, the block Checks can add when a page has no Heading 1. Nothing the builder does inserts placeholder text.
  • blockRoot puts a block's own element, such as an li, where Puck's wrapper would go, so a slot of blocks can be a ul of li.

ContentBuilder

'use client';
import { ContentBuilder } from '@setsquare/ui-blocks';

<ContentBuilder
  config={blocks}
  data={page}
  breadcrumbs={[{ label: 'Pages', href: '/pages' }]}
  preview={{ href: `/preview/${page.id}` }}
  canvas={{ stylesheets: ['/site/theme.css'] }}
  onChange={saveDraft}
  onPublish={publish}
/>
  • Its own width. It lays itself out by its own width, not the window's. From 1280 px: Outline and Blocks in one panel, the canvas, and Settings, side by side. From 1024 px a rail opens Outline or Blocks beside the canvas; from 768 px, one panel at a time, Settings included; below that, one view at a time. Panels push the canvas, never cover it.
  • Three ways to do everything. Add a block by dragging it, by pressing it in the library, or with Enter. Move one by dragging, with the block toolbar or the row's Actions menu, with Move to… (no drag at all), with Alt+Shift+↑ and ↓, or by a keyboard drag from the row's drag button.
  • Focus and words. After every change, focus goes somewhere named, never to the page, and one live region says what happened and where ("Image moved into Left column, 2 of 2"). F6 moves between the header, the page structure, the canvas and Settings, from inside the canvas too.
  • The canvas is the page at its real size, in your site's styles (canvas.stylesheets and canvas.attributes), never scaled. In Edit its links and buttons are inert and its media still; Interact makes the page work.
  • Checks run as the page is edited, on the page as it renders. Each finding says what, where and how to fix it, often in one press, and Undo takes a fix back. While anything is under Must fix, Publish is locked: still focusable, with the reason in its name. rules sets each rule's severity, or turns it off; Checks still lists a rule that's off, with what that risks, and onRulesChange lets an author turn it back on.
  • Publishing opens a review of the changes since the last publish. onPublish gets the page and its report, and only when nothing is under Must fix. Check again on the server (below).
  • Saving. onChange is called with the page after every change. Return a promise and the header shows Saving until it settles.
  • Sessions. session={{ key: page.id, expiresAt, extend, onExpired }} warns at least two minutes before the session ends, with a single Stay signed in. With key, the page, the selection and the history are kept in this browser for at least 21 hours, and come back after signing in again.
  • Help has the tour, the keyboard shortcuts (people can change them; shortcuts and onShortcutsChange keep theirs), what the checks look for, and "Words used here" (add your own with glossary). The tour opens by itself for a person who hasn't seen it when you pass tour: see the help tour.
  • Words. Every word it says comes from labels, with {name} placeholders, over English defaults (defaultLabels).
  • Puck. puck passes plugins, permissions and viewports to Puck. The rest is the builder's: see Using it with Puck.
  • On a phone, narrow and touch, a notice asks for a bigger screen and leads into outline view. Width alone never shows it, so a laptop at 400% zoom goes straight to the outline.

A layout of your own

ContentBuilder's children replace its layout. Build one from its surfaces: BuilderHeader, OutlineTree, OutlineSearch, BlockLibrary, BuilderCanvas and Inspector, and place the dialogs the outline, the toolbar and Help open: MoveToDialog, ChecksDialog, ShortcutsDialog and GlossaryDialog. useBuilder() gives the page, the selection and every command (insert, move, duplicate, remove, undo, redo), each named in the history, announced, and saying where focus goes. useBuilderShell() gives the layout's state: which panel is open, the dialogs, the checks.

Checks on the server

The lock in the editor is only as strong as the server behind it. runChecks runs the same rules on the HTML your publish endpoint is about to serve, so the endpoint can refuse a page with must-fix issues even when a request skips the editor:

import { renderToStaticMarkup } from 'react-dom/server';
import { Render } from '@setsquare/ui-blocks/render';
import { runChecks } from '@setsquare/ui-blocks/checks';

const html = renderToStaticMarkup(<Render config={blocks} data={page} />);
const report = runChecks(html, { title: page.root.props.title, rules });
if (report.must > 0) return Response.json(report, { status: 422 });

builtInRules lists the rules and their default severities, each with what it rests on: a WCAG criterion, or a house rule where WCAG advises rather than requires. defineRule writes a block's own, with its fixes.

The published page

import { Render, resolveAllData } from '@setsquare/ui-blocks/render';

const data = await resolveAllData(page, blocks, metadata);
return <Render config={blocks} data={data} metadata={metadata} />;

Server-safe: nothing in ./render is a client module. Render leaves out the editor's own props, so nothing the builder keeps on a block reaches the page.

What's checked

"Checked" here means a check in this repository asserts it. Automated checks aren't conformance, and nothing has been tested with a real screen reader yet: a script for people to follow is ready for that.

  • Every task, three ways (the blocks browser suites). Insert, select, move up and down, into and out of a column with Move to… and with a keyboard drag, duplicate, delete, undo, fix and publish:

    • from the keyboard alone, checking the page's data and where focus is after every step;
    • with clicks alone, with a guard that fails if the pointer moves while a button is held;
    • by dragging blocks on the canvas, and from the library onto it.

    Still presses of 150 to 1000 ms with a mouse, a finger and a pen add a block and never start a drag.

  • Each suite runs eight times: AA and AAA, light and dark, and two brands.

  • Words and focus. Every announcement is compared with the expected words, in English and with French labels. Shortcuts are run with US, German and French keyboard layouts' keys. Focus appearance (2.4.13 at AAA), focus not obscured (2.4.11 and 2.4.12) with a toast and each panel open, forced colours, 1.4.12's text spacing, and zoom at 200% and 400%.

  • Part B. The H1 rule catches a hard-coded <h1>, one in rich text and a header that makes the site's name one. A file name is never offered as alt text. Pasting from Word and Google Docs keeps what it should. The demo's blocks pass axe at the level they claim, once Checks' fixes are applied. The editor's checks and runChecks agree, and a publish endpoint built as above, in a real Next.js app, refuses must-fix issues.

  • Puck. A contract test for each Puck behaviour the builder relies on or works around, in Node and in the browser, so an upgrade that changes one fails first.

  • The rest of the repository's guards reach this package: the lint rules (client-directive, and no-builder-surface-override for products), the layers, positioning and axe suites on every builder card, the docs site's page suite, and package:verify, which installs the packed package into a fresh Next.js app.

Licence

Apache-2.0. Puck (@puckeditor/core) is MIT-licensed and is installed alongside this package, not bundled into it, with the Tiptap, dnd-kit and Radix packages it depends on. parse5 (MIT), for reading HTML in ./checks, and lucide-react (ISC), for icons, are dependencies.