Kodza

Your editor

Terminal
$ claude mcp add -t http kodza https://kodza.dev/api/mcp

Nothing to install: the server runs on kodza.dev. Added this way, it's for the current project; --scope user right after add makes it yours everywhere. claude mcp list should show it.

Rather keep the search on your machine? The same server runs locally, with npx -y kodza-mcp as the command (Node 18 or newer): it's also what Claude Desktop's config file takes. If the editor can't find Node, give command the full path from which npx, and the server "env": { "PATH": "…" } with that folder first.

Then just ask

In your own words, in the chat you already use. Your AI searches Kodza, then pastes the real code where it goes.

  • add a button that fills from the cursor
  • a sticky header that hides while I read, in my WordPress theme
  • a date picker for this form

Every component page has its own sentence under With your AI, ready to copy.

What your AI gets

Two tools. Your AI picks them on its own; you never call them by name.

search_components
Finds components by what they do or look like, or lists a whole category. Returns slugs.
get_component
One component: what it does, its API (variables and their defaults, modifiers, data- attributes, events), its dark-mode values, how to install it and the full code. With a target (html, artifact, wordpress, shopify, react), only the notes for that stack.

The server also tells your AI how to behave: paste the code as it is, guard included, and restyle through the variables instead of rewriting the CSS. Building a Claude artifact? It asks for target: "artifact", because artifacts block images from other sites.

Running your own copy? KODZA_REGISTRY points the server somewhere else than kodza.dev: a URL, or a local folder holding registry/ and components/.

Get the code

Every component page has View the code: HTML, CSS and, when it has one, JS. Copy takes the tab you're on. Download ZIP gives you the whole demo as three files, index.html, styles.css and script.js: open the page, it runs.

No JS tab? It's CSS only. The catalogue has a filter for those.

Images in the code point to kodza.dev/media/. Swap them for yours before you ship.

Where each piece goes

  1. CSS

    In a <style> in <head>, or in your stylesheet. It only touches kodza- classes.

  2. HTML

    Where the component goes. Paste it as many times as you need: instances never interfere.

  3. JS

    In a <script> before </body>, or in <head>: it waits for the page. One copy per page, however many instances.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <style>/* the component's CSS, guard included */</style>
</head>
<body>
  <h1>Works on my machine.</h1>

  <!-- the component's HTML, as many times as you like -->

  <!-- the component's JS, once -->
  <script>/* … */</script>
</body>
</html>

No build step, no import, no npm install. It's just code.

Keep the guard

The CSS ends with a block full of :not(#_). Keep it. It's a theme guard: it stops a site's own CSS (WordPress themes, Bootstrap, resets) from restyling the component's buttons and fields.

End of the CSS
/* kodza-guard 6064203f8c2e — generated by Kodza, do not edit. */
.kodza-fill-button:not(#_) {
  box-sizing: revert;
  appearance: revert;
  max-height: revert;
  /* … */
}

:not(#_) is always true and weighs as much as an id, so these lines beat theme rules made of classes and tags. The guard is generated and measured in Chrome: the demo looks the same to the pixel with it and without it. It leaves the variables alone, so you still restyle through them.

The font

Components take your page's font. The Kodza look is Geist and Geist Mono, from Google Fonts:

<head>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap">

The HTML: a Custom HTML block

Or a pattern, or a template part. WordPress keeps the markup as it is only for users allowed to post unfiltered HTML: Administrators, and Super Admins on a multisite. For anyone else it strips tags such as <svg>, <input> or <dialog>, so add it from the theme instead (a pattern or a template part).

The CSS: Additional CSS

Appearance → Customize → Additional CSS in a classic theme, Styles → Additional CSS in the Site Editor for a block theme, or your theme's stylesheet. It's scoped to kodza- classes: it never touches the theme.

One exception: CSS that declares a typed property (an @property with a <length> syntax) looks like markup to WordPress, and Additional CSS refuses it. Those components say so in their WordPress notes: enqueue their CSS from the theme, with wp_enqueue_style.

The JS: enqueued in the footer

Save it as a file in your child theme (here kodza/button-fill.js), then enqueue it from its functions.php, or from a code-snippets plugin. One copy, however many blocks.

functions.php
add_action('wp_enqueue_scripts', function () {
  $file = '/kodza/button-fill.js';
  wp_enqueue_script(
    'kodza-button-fill',
    get_stylesheet_directory_uri() . $file,
    [],
    filemtime(get_stylesheet_directory() . $file), // a new version when the file changes
    true // in the footer
  );
});

A child theme, not the parent: the next theme update would wipe your changes.

Full width: the Group block

A block theme keeps a Custom HTML block to the width of the text column. A component that spans the page goes inside a Group block set to Full width or Wide width, with Inner blocks use content width turned off.

The other way round, an inline component (a button, a link) pasted alone in a block theme sits at the edge of the page. Put its Custom HTML block inside a Group block to line it up with the text.

Good to know

  • WordPress turns straight quotes and -- into typographic ones in the text of a Custom HTML block. Not inside <code> or <pre>.
  • A theme with Ajax page transitions? Fire kodza:init on the new content after each swap, and kodza:destroy on the old one. Chapter 05.
  • To match the theme instead of Kodza's monochrome, point the colour variables at its palette, on the component root: --kz-fill-fg: var(--wp--preset--color--contrast);
  • Some components come with their own WordPress note (where a sticky header goes, which wrapper to name). It's in their Markdown, and in what the MCP hands your AI.

Tested in eight themes. Not in all of them.

Before it ships, every component goes into a real WordPress, the latest one, pasted exactly the way this chapter says. Eight times:

Made by WordPress
Twenty Twenty-Five, a block theme, and Twenty Twenty-One, a classic one.
Among the most installed
Astra, Hello Elementor, GeneratePress, Kadence, OceanWP and Blocksy.

Then Chrome opens each one on a laptop and a phone: no JavaScript error, nothing wider than the screen, no theme style leaking in, a clean restart after an Ajax swap. Menus and dialogs are opened for real, then closed with Escape.

There are thousands of other themes, and some restyle every button they can reach. We can't test them all. The guard beats theme rules made of classes and tags; a rule with an id or an !important can still get through. It works on our eight machines: look at it once in yours before you ship.

Variables

Every setting is a CSS variable, --kz-<component>-…, at the top of the component's CSS, and listed with its default in its Markdown. Set them on the component's root element itself: the root declares its own defaults, so a value on a parent is ignored.

your.css
.kodza-fill-button {
  --kz-fill-fill: var(--brand);
  --kz-fill-radius: 12px;
  --kz-fill-duration: 0.4s;
}

/* Ignored: the button sets its own value */
.hero {
  --kz-fill-fill: var(--brand);
}

Variants

A variant is a modifier class (kodza-fill-button--solid) or a data- attribute. The component's Markdown lists them under API, with example values.

index.html
<a class="kodza-fill-button kodza-fill-button--solid" href="/start">
  <span class="kodza-fill-button__label">Get Kodza</span>
</a>

Dark mode

Every component's Markdown has a Dark mode section: the values Kodza uses on a dark background (#0a0a0a), taken from its own demo. Three ways to use them:

your.css
/* 1. The page follows the system theme */
@media (prefers-color-scheme: dark) {
  .kodza-fill-button {
    --kz-fill-fg: #ffffff;
    --kz-fill-border: rgba(255, 255, 255, 0.85);
    --kz-fill-fill: #ffffff;
    --kz-fill-fill-fg: #0a0a0a;
  }
}

/* 2. Your site has its own theme switch */
[data-theme="dark"] .kodza-fill-button { /* same values */ }

/* 3. Only one section is dark */
.site-footer .kodza-fill-button { /* same values */ }

Some need nothing: they take their colour from the text around them. Images have dark versions next to them (kz-block-1400.webp → kz-block-dark-1400.webp), for a <picture> with a prefers-color-scheme source.

We eat our own cooking. The code blocks on this page's dark bands use the Code Block's own dark values, placed with recipe 3.

Reduced motion

Nothing to do. Every component that moves answers prefers-reduced-motion: the motion is cut or made instant, and the component still works. Our checker refuses a component that moves without it.

To see it for yourself: System Settings → Accessibility → Display → Reduce motion on a Mac, Settings → Accessibility → Visual effects → Animation effects on Windows, or Chrome DevTools, Rendering → Emulate CSS media feature prefers-reduced-motion.

The two events

your.js
// Start the components inside a container (the whole page without one)
document.dispatchEvent(new CustomEvent('kodza:init', { detail: { root: container } }));

// Before removing it: stop them and put their original markup back
document.dispatchEvent(new CustomEvent('kodza:destroy', { detail: { root: container } }));

Every component with JS listens for both, the same way. Whatever an instance starts (listeners, observers, timers) stops on kodza:destroy. Calling kodza:init twice is harmless: a running instance is skipped.

Ajax themes and Swup

Whatever does the swap (an Ajax theme, Swup, Barba, Turbo), the rule is the same: kodza:destroy on the old content before it goes, kodza:init on the new content once it's in. With Swup 4:

your.js
const kodza = (type) => () => {
  const root = document.querySelector('#swup');
  document.dispatchEvent(new CustomEvent(type, { detail: { root } }));
};

swup.hooks.before('content:replace', kodza('kodza:destroy'));
swup.hooks.on('page:view', kodza('kodza:init'));

React, StrictMode included

Import the CSS once. Render the HTML as it is, so React doesn't fight the script over that markup, and start it in an effect:

FillButton.jsx
'use client';
import { useEffect, useRef } from 'react';

export function FillButton() {
  const ref = useRef(null);
  useEffect(() => {
    const root = ref.current;
    document.dispatchEvent(new CustomEvent('kodza:init', { detail: { root } }));
    return () =>
      document.dispatchEvent(new CustomEvent('kodza:destroy', { detail: { root } }));
  }, []);
  return <div ref={ref} dangerouslySetInnerHTML={{ __html: html }} />;
}

Load the script once, after React has hydrated the page. In Next.js, that's <Script> from next/script in the root layout. Not a plain <script> tag: it rewrites the markup before hydration, and React files a complaint on every page. Anywhere else, import() it in the effect.

StrictMode mounts twice in development. No harm done: kodza:destroy puts the markup back before the second mount. Tested in a real Next.js app, dev and production, client-side navigation included. Because "works on my machine" is not a test plan.

Shopify

The theme editor re-renders sections as you edit them. Restart them:

your.js
const kodza = (type) => (e) =>
  document.dispatchEvent(new CustomEvent(type, { detail: { root: e.target } }));

document.addEventListener('shopify:section:load', kodza('kodza:init'));
document.addEventListener('shopify:section:unload', kodza('kodza:destroy'));

That's it. Ship it.

The rest, your AI can fetch.

claude mcp add -t http kodza https://kodza.dev/api/mcp

Also in Codex, Cursor and VS CodeBrowse 108 components