@goodfellow-cms/registry
Goodfellow's block registry: blocks built with shadcn/ui, and a few built with Tailwind alone for some sites only, which a site's admins add and remove on the admin panel's Blocks screen, with no developer. It's also the reference for anyone publishing a registry of Goodfellow blocks built with another component library.
| Block | Category | Recommended |
|---|---|---|
| Hero | Page sections | Yes |
| Cards | Page sections | Yes |
| Call to action | Page sections | Yes |
| Testimonials | Page sections | Yes |
| Notice | Page sections | Yes |
| Pricing table | Page sections | |
| FAQ | Interactive | Yes |
| Tabs | Interactive | Yes |
| News feed | Page sections | |
| Map | Page sections | |
| Social links | Page sections | |
| Panel | Page sections | |
| Image carousel | Interactive | |
| Slideshow with quick links | Interactive | |
| Content with section links | Navigation | |
| People | Page sections | |
| Logos | Page sections | |
| Numbers | Page sections | |
| Timeline | Page sections | |
| Countdown | Interactive | |
| Comments | Interactive | |
| Custom HTML | Advanced |
New sites made with create-goodfellow start with the recommended blocks unless the admin chooses "Built-in blocks only".
How a block gets into a site
Adding a block copies its code into the site, as npx shadcn add would, in one publish:
- the block's own files to
blocks/installed/<name>/; - the shadcn components it uses to
components/ui/, and any other shared code tocomponents/,lib/orhooks/, unless the site already has them; - a record of what was added,
blocks/installed/installed.json, with a hash of every file written; blocks/installed/index.ts, which the site'sgoodfellow.config.tsximports and which lists every installed block.
The editor offers the block once the site has been rebuilt with it, which the host does after the publish (and goodfellow dev or next dev do straight away). Removing a block is refused while any page, collection page design, header or footer uses it. Removing deletes the files it added, except those someone has changed since or that another installed block uses.
The admin panel installs a release of this registry that matches its own: https://cdn.jsdelivr.net/npm/@goodfellow-cms/registry@<version>/r/{name}.json. In development, goodfellow dev and next dev serve the copy in the site's node_modules.
Publishing your own registry
A Goodfellow block registry is a shadcn registry whose block items follow a few extra rules. Build it with npx shadcn build and host the files anywhere that allows requests from browsers (CORS), such as GitHub Pages, npm through jsDelivr, or your own site.
Items
A block is a registry:block item with meta.goodfellow:
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "acme-gallery",
"type": "registry:block",
"title": "Gallery",
"description": "Pictures in a grid that open larger when clicked.",
"registryDependencies": ["@acme/dialog"],
"dependencies": ["radix-ui"],
"files": [
{ "path": "blocks/acme-gallery/block.tsx", "type": "registry:block", "content": "…" },
{ "path": "blocks/acme-gallery/gallery-grid.tsx", "type": "registry:component", "content": "…" }
],
"meta": {
"goodfellow": {
"category": "Pictures",
"recommended": false,
"image": "images/acme-gallery.png",
"version": "1.2.0"
}
}
}
namebecomes the block's key in the site's content, so never change it once published. Use lowercase letters, numbers and dashes, and start names with your registry's own prefix so they don't clash with other registries' blocks.titleanddescriptionare what admins see on the Blocks screen. Write them for people who aren't developers.meta.goodfellow.categoryis the group the block is listed under, on the Blocks screen and in the editor.recommendedmarks blocks a new site should start with.imageis a picture of the block, relative to the item's address.versionis recorded in the site when the block is added.- Files: the item has exactly one
registry:blockfile, whose default export is the block's PuckComponentConfig. Every other file of the item goes into the block's folder too (blocks/installed/<name>/), so import them with relative paths (./gallery-grid). Items withoutmeta.goodfellow, such as shared components, are dependencies: their files go tocomponents/ui/(registry:ui),components/(registry:component),lib/(registry:lib) orhooks/(registry:hook), and blocks import them as@/components/ui/dialogand so on. A file'stargetcan name another place within those folders. Other file types, and files elsewhere in the site, are refused. registryDependenciesname items as@namespace/name, or as a plain name for shadcn/ui's own components ("button"), as with the shadcn CLI.registry.json, at the same address as the items (…/r/registry.jsonfor…/r/{name}.json), lists them, so the Blocks screen can show them without loading every item.
Packages
The admin panel can't install npm packages, so every site includes the same set and blocks can use only those. Besides react, @goodfellow-cms/core, @goodfellow-cms/react and @puckeditor/core, which every site has, they are class-variance-authority, clsx, cn, embla-carousel-react, lucide-react, radix-ui, tailwind-merge and tw-animate-css. An item whose dependencies name anything else can't be added. To suggest a package for the list, open an issue.
Writing the blocks
Registry blocks follow the same rules as any Goodfellow block (see AGENTS.md):
- Every block has a
classNameprop (className: classNameField), applied to its outermost element and combined withcx()from@goodfellow-cms/react, last. - Use the theme's classes (
bg-primary,text-muted-foreground,rounded-lg), which shadcn's components use too, so blocks follow the site's colors and fonts. - Give it words to be found by in the editor's block search, besides its label:
export default withKeywords(block, ["faq", "questions"])from@goodfellow-cms/react, lowercase. It can wrapwithParts(), or the other way round. - Name the block's parts, so a site's theme can restyle them:
const part = useParts()at the start ofrender, thenclassName={part("gf-my-block-title", "text-xl font-bold", className)}on each part (the block's own classes, then anything an editor chose,classNamelast), and declare them with what owners call them:export default withParts(block, { "gf-my-block-title": "Title" }). Start every name withgf-and the item's name, so they never clash with another block's, and never rename or remove one once it's published, since themes refer to them. Client Components get their parts' classes as plain props from the block, asshadcn-faqdoes. - Render links and images with
SiteLinkandSiteImagefrom@goodfellow-cms/react, and image fields withmediaField(). To style a link as a button, giveSiteLinkthe classes from shadcn'sbuttonVariants(). - Labels and options are for non-technical people: "Space above and below", not "padding-y".
renderuses no hooks besidesuseSite(). Interactive parts go in a file starting with"use client", which gets plain values and content (children, rich text) only.- One Client Component per interactive part. Sites built with
goodfellow buildrun each Client Component a block renders as its own small React app (an island), and islands don't share React context. Components whose parts talk to each other through context, as Radix'sAccordion,AccordionItemandAccordionContentdo, must all be put together inside one"use client"file, which the block renders with its content as props. Seesite/blocks/installed/shadcn-faq/here. - Keep content in the page. Hidden content, such as closed answers or other tabs, should still be in the HTML (Radix's
forceMount, hidden with CSS), so search engines and visitors without JavaScript see it. - Prefer HTML and CSS where they're enough: a
<details>element rather than a Client Component, since a page with a Client Component loads React. - Say when a Client Component works with Preact, with
export const worksWithPreact = truein its"use client"file, once you've tried it there. Sites whose Client Components all say so run them with Preact'spreact/compatinstead of React, about a tenth of its size; one that doesn't keeps a site on React. Every interactive block here says so, and Goodfellow's tests try each with Preact.
Letting a site use it
Admins can only add blocks from registries the site's developer trusts, since a block's code runs in the admin panel with the editor's access to the site. Add yours to the site's goodfellow.config.tsx, and its blocks appear on the Blocks screen beside Goodfellow's:
export default defineConfig({
blocks: { ...blocks, ...installedBlocks },
registries: { "@acme": "https://acme.example/r/{name}.json" },
});
Making it easy to find
Call the registry's repository goodfellow-blocks-<name> and give it the goodfellow-blocks topic on GitHub or GitLab, so searching either finds it, as goodfellow-plugin-<name> and goodfellow-theme-<name> do for plugins and themes. You can also ask for it to be listed in shadcn's directory of registries. This registry is the exception, since it's released with Goodfellow.
Working on this registry
registry.jsonlists the blocks, whose files are insite/blocks/installed/, laid out as they'll be in a site.site/components/ui/andshadcn.jsonare a snapshot of the shadcn/ui components the blocks use, so a release always installs the same code.pnpm --filter @goodfellow-cms/registry update-shadcn(scripts/snapshot.mjs) fetches them again; review and commit what it writes. Don't edit the snapshot by hand.pnpm build(scripts/build.mjs) writesr/, which is what's published: an item per block and component, with its files, andr/registry.json.- The tests check that every item is valid, uses only the allowed packages, installs with every file it imports, and renders.
The shadcn/ui components are © shadcn, under the MIT license in LICENSE-shadcn.md.