Skip to content
Meeko
Type to search the guides and the API reference. Open it from any page with / or Ctrl K; move with the arrow keys and open a result with Enter.
Get started

Contents

On this page

meekoUtilities

PostCSS plugin that replaces each @utilities "<ruta>" with CSS rules generated by a TypeScript function, with HMR in meeko dev and the project's auto-imports.

advanced API Since 1.2.0 @meekojs/meeko/postcss Source code
ts
import { meekoUtilities } from "@meekojs/meeko/postcss";

meekoUtilities() is a PostCSS plugin that generates CSS with TypeScript. Where a stylesheet contains @utilities "./utilities/color.ts" layer(utilities);, the plugin imports that module, calls its default export function (usually defined with defineUtilities) and replaces the at-rule with the returned rules, optionally inside @layer. It is the typed replacement for preprocessor loops (@each, @for): a TypeScript map over a palette or scale.

It works in the global stylesheet (app/styles/main.css), in files it imports and in a .meeko file's <style>. With Vite 8, the module is bundled with rolldown on each change, so editing it, or a local module it imports, regenerates CSS in meeko dev without restarting. Since 1.4, in a Meeko project the module receives auto-imports from app/ and shared/.

Usage#

ts
// postcss.config.ts
import { meekoUtilities } from "@meekojs/meeko/postcss";

export default { plugins: [meekoUtilities()] };
css
/* app/styles/main.css */
@utilities "./utilities/buttons.ts" layer(utilities);
ts
// app/styles/utilities/buttons.ts
import { defineUtilities } from "@meekojs/meeko/postcss";

const sizes = { sm: "0.25rem 0.5rem", md: "0.5rem 1rem" };

export default defineUtilities(({ rule }) =>
  Object.entries(sizes).map(([size, padding]) => rule(`.button[data-size='${size}']`, { padding })));

Result:

css
@layer utilities {
  .button[data-size='sm'] { padding: 0.25rem 0.5rem; }
  .button[data-size='md'] { padding: 0.5rem 1rem; }
}

Type#

ts
function meekoUtilities(options?: MeekoUtilitiesOptions): Plugin

MeekoUtilitiesOptions

ts
interface MeekoUtilitiesOptions {
	readonly root?: string
	readonly autoImports?: boolean
}

Options#

Option Type Default Description
root path process.cwd() Directory from which @utilities paths are resolved when the stylesheet has no file (CSS processed without PostCSS's from option). With a file, the path is always relative to the stylesheet.
autoImports boolean true Since 1.4. Applies auto-imports from app/ and shared/ to @utilities modules and their local imports, executing meeko.config.* once per stylesheet. false bundles modules as in 1.3, without executing configuration.

The at-rule accepts a quoted path to a .ts, .mts, .js or .mjs file, relative to the containing stylesheet, and optional layer(<nombre>). It has no block and must follow @import rules.

Return value#

A PostCSS plugin (postcssPlugin: "meeko-utilities"). Each @utilities is replaced with the rules returned by the function: a rule, atRule or css node, or an array of them (nested to any depth), where null, false and undefined are omitted.

Errors stop the stylesheet with the CSS file and the @utilities line, which Vite shows in its overlay:

texto
meeko-utilities: …/app/styles/main.css:1:13: "./bad.ts": la salida contiene ".x { color: red }", que no es un nodo de utilidades; envuelva el CSS literal con css`…`

Examples#

A palette with variables per color#

ts
// app/styles/utilities/color.ts
import { defineUtilities } from "@meekojs/meeko/postcss";

const colors = ["gray", "blue", "red"] as const;
const steps = Array.from({ length: 12 }, (_, index) => index + 1);

export default defineUtilities(({ rule, atRule }) => [
  colors.map((name) =>
    rule(`.color[data-color='${name}']`, Object.fromEntries(steps.map((step) => [`--color-${step}`, `var(--${name}-${step})`])))),
  rule(".color", { color: "var(--color-11)" },
    rule("&:hover", { backgroundColor: "var(--color-3)" }),
    atRule("media", "(prefers-contrast: more)", { color: "var(--color-12)" })),
]);

With Tailwind#

ts
// postcss.config.ts
import { meekoUtilities } from "@meekojs/meeko/postcss";
import tailwindcss from "@tailwindcss/postcss";

export default { plugins: [meekoUtilities(), tailwindcss()] };

meekoUtilities() goes before @tailwindcss/postcss so Tailwind sees the generated rules. @utilities does not conflict with Tailwind 4's @utility.

Outside Vite#

sh
npx postcss app/styles/main.css -o dist/main.css

With postcss-cli, if rolldown is not found, the plugin imports the module using Node's type stripping (imports with extensions and erasable TypeScript), reloads only the entry module and reports this once.

Common mistakes#

Returning CSS as text#

Incorrectts
// app/styles/utilities/bad.ts
export default () => [".x { color: red }"];

"./bad.ts": la salida contiene ".x { color: red }", que no es un nodo de utilidades; envuelva el CSS literal con css…``.

Correctts
// app/styles/utilities/good.ts
import { defineUtilities } from "@meekojs/meeko/postcss";

export default defineUtilities(({ css }) => css`.x { color: red; }`);

A path that does not exist#

Incorrectcss
@utilities "./utilities/colours.ts";

@utilities no encuentra "./utilities/colours.ts" (C:\proyectos\tienda\app\styles\utilities\colours.ts). The path is relative to the stylesheet containing the at-rule, not the project.

Correctcss
@utilities "./utilities/color.ts";

@utilities before an @import#

Incorrectcss
@utilities "./utilities/color.ts" layer(utilities);
@import "./tokens.css";

In CSS, @utilities is another rule, and @import rules must precede every rule: Vite warns (@import statements must precede all other statements) and does not include that @import.

Correctcss
@import "./tokens.css";
@utilities "./utilities/color.ts" layer(utilities);

Notes#

  • Node only. The plugin runs during meeko dev and builds; @meekojs/meeko/postcss is not part of the browser or server runtime.
  • Scope. Rules generated in a <style scoped> do not receive the scope attribute: they are global, because PostCSS generates them after Meeko applies scoping.
  • Reloading. Keep these modules in app/styles/: in directories Meeko watches for auto-imports and routes (app/components, app/utils, shared/…), a change reloads the entire page instead of updating only the CSS.
  • Auto-imports. Modules execute in Node when generating CSS: they receive value exports from app/ and shared/, but not those from server/, components, generated clients or framework APIs such as signal or useState. Using one of those throws an error naming the auto-import and its source.
  • Types. Declarations for rule() and atRule() use csstype since 1.3: backgroundColr or padding: 4 are type errors.

Was this page helpful?

Add a comment

It is sent when you choose “Yes” or “No”. Only the page, your answer and the comment are stored: no cookies and no personal data.

0 of 2000 characters

Edit on GitHub: docs/en/reference/advanced/meekoutilities.md