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.
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#
// postcss.config.ts
import { meekoUtilities } from "@meekojs/meeko/postcss";
export default { plugins: [meekoUtilities()] };/* app/styles/main.css */
@utilities "./utilities/buttons.ts" layer(utilities);// 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:
@layer utilities {
.button[data-size='sm'] { padding: 0.25rem 0.5rem; }
.button[data-size='md'] { padding: 0.5rem 1rem; }
}Type#
function meekoUtilities(options?: MeekoUtilitiesOptions): Plugininterface 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:
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#
// 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#
// 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#
npx postcss app/styles/main.css -o dist/main.cssWith 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#
// 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…``.
// 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#
@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.
@utilities "./utilities/color.ts";@utilities before an @import#
@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.
@import "./tokens.css";
@utilities "./utilities/color.ts" layer(utilities);Notes#
- Node only. The plugin runs during
meeko devand builds;@meekojs/meeko/postcssis 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/andshared/, but not those fromserver/, components, generated clients or framework APIs such assignaloruseState. Using one of those throws an error naming the auto-import and its source. - Types. Declarations for
rule()andatRule()use csstype since1.3:backgroundColrorpadding: 4are type errors.