# tailwind-utopia

`tailwind-utopia` is a v4-native generator for fluid typography and spacing inspired by Utopia.

It does NOT ship a legacy Tailwind JS plugin. Instead, it reads a project-level `tailwind-utopia.config.js`, generates a CSS file, and Tailwind CSS v4 consumes that CSS with `@import`.

That config file is the generator contract and the project-level source of truth.

## What it generates

- Internal runtime variables in `:root`
- Public utilities via `@utility`
- Fluid text steps like `text-fluid-base`, `text-fluid-3xl`
- Fluid spacing utilities like `mt-fluid-sm`, `px-fluid-lg`, `gap-fluid-xs`, `space-y-fluid-sm-md`
- Contiguous spacing pairs by default, plus optional custom pairs

## Install

```bash
npm install --save-dev @andreibratila/tailwind-utopia
```

## Tailwind v4 flow

1. Create the generator config:

```bash
npx tailwind-utopia config
```

2. Generate the CSS file:

```bash
npx tailwind-utopia generate
```

3. Import the generated CSS from your main Tailwind stylesheet:

```css
@import "tailwindcss";
@import "./tailwind-utopia.css";
```

4. Use the utilities in markup:

```html
<h1 class="text-fluid-4xl mb-fluid-lg">Fluid heading</h1>
<div class="px-fluid-sm md:px-fluid-lg">Responsive content</div>
<section class="space-y-fluid-sm-md">...</section>
<div class="-space-x-fluid-xs-lg">...</div>
```

## CLI

```bash
tailwind-utopia config [--out path]
tailwind-utopia generate [--config path] [--out path] [--stdout]
```

Examples:

```bash
npx tailwind-utopia config
npx tailwind-utopia generate
npx tailwind-utopia generate --out ./src/styles/tailwind-utopia.css
```

## Config

The generator reads `tailwind-utopia.config.js` from the current working directory by default. If the file is missing, built-in defaults are used.

Recommended shape:

```js
import { defineConfig } from "@andreibratila/tailwind-utopia";

export default defineConfig({
  prefix: "fluid",
  output: "./tailwind-utopia.css",
  typography: {
    minWidth: 320,
    maxWidth: 1140,
    minSize: 18,
    maxSize: 20,
    minScale: 1.2,
    maxScale: 1.25,
    baseStep: "base",
    steps: {
      xs: { lineHeight: 1.4 },
      sm: { lineHeight: 1.45 },
      base: { lineHeight: 1.5 },
      lg: { lineHeight: 1.45 },
      xl: { lineHeight: 1.3 },
      "2xl": { lineHeight: 1.2 },
      "3xl": { lineHeight: 1.1 },
      "4xl": { lineHeight: 1 }
    }
  },
  spacing: {
    enabled: true,
    scale: {
      "3xs": 0.25,
      "2xs": 0.5,
      xs: 0.75,
      sm: 1,
      md: 1.5,
      lg: 2,
      xl: 3,
      "2xl": 4,
      "3xl": 6
    },
    pairs: "contiguous",
    customPairs: [],
    utilities: {
      m: ["margin"],
      mx: ["margin-left", "margin-right"],
      my: ["margin-top", "margin-bottom"],
      mt: ["margin-top"],
      mr: ["margin-right"],
      mb: ["margin-bottom"],
      ml: ["margin-left"],
      "-m": ["margin"],
      "-mx": ["margin-left", "margin-right"],
      "-my": ["margin-top", "margin-bottom"],
      "-mt": ["margin-top"],
      "-mr": ["margin-right"],
      "-mb": ["margin-bottom"],
      "-ml": ["margin-left"],
      p: ["padding"],
      px: ["padding-left", "padding-right"],
      py: ["padding-top", "padding-bottom"],
      pt: ["padding-top"],
      pr: ["padding-right"],
      pb: ["padding-bottom"],
      pl: ["padding-left"],
      "space-x": [],
      "space-y": [],
      "-space-x": [],
      "-space-y": [],
      gap: ["gap"],
      "gap-x": ["column-gap"],
      "gap-y": ["row-gap"],
      w: ["width"],
      h: ["height"],
      inset: ["top", "right", "bottom", "left"],
      "inset-x": ["left", "right"],
      "inset-y": ["top", "bottom"],
      top: ["top"],
      right: ["right"],
      bottom: ["bottom"],
      left: ["left"],
      "-inset": ["top", "right", "bottom", "left"],
      "-inset-x": ["left", "right"],
      "-inset-y": ["top", "bottom"],
      "-top": ["top"],
      "-right": ["right"],
      "-bottom": ["bottom"],
      "-left": ["left"]
    }
  }
});
```

Notes:

- `defineConfig()` is optional at runtime, but useful as the canonical config wrapper.
- The file generated by `tailwind-utopia config` imports `defineConfig` from `@andreibratila/tailwind-utopia`, so this package must be installed in the consuming project (usually as a dev dependency).
- If you want zero wrapper friction, replace `defineConfig(...)` with a plain object export (`export default { ... }`).
- `prefix` is normalized to a trailing `-`, so `fluid` becomes `fluid-` internally.
- `typography.steps` replaces the default step map when you provide it.
- `spacing.scale` replaces the default spacing scale when you provide it.
- `spacing.pairs` only accepts `"contiguous"` or `false`.
- `spacing.customPairs` is where you opt into non-contiguous pairs such as `"xs-lg"`.
- `spacing.utilities` overrides or extends the built-in utility map; each value can be a string or an array of CSS properties.

The `tailwind-utopia config` command writes this full contract to disk so the file stays explicit.

Plain-object alternative (no `defineConfig` import):

```js
export default {
  prefix: "fluid",
  output: "./tailwind-utopia.css",
  // ...rest of your config
};
```

Generated default config:

```bash
npx tailwind-utopia config
```

### Typography

- `baseStep` decides which entry is the modular-scale origin.
- Each step can override `min`, `max`, and `lineHeight`.
- Step order comes from the object key order in `typography.steps`.
- All numeric typography fields must be finite positive numbers.

Example custom typography:

```js
export default {
  typography: {
    minSize: 16,
    maxSize: 18,
    minScale: 1.18,
    maxScale: 1.3,
    baseStep: "base",
    steps: {
      sm: { lineHeight: 1.5 },
      base: { lineHeight: 1.6 },
      lg: { lineHeight: 1.45 },
      xl: { lineHeight: 1.25 },
      hero: { min: 40, max: 72, lineHeight: 0.95 }
    }
  }
};
```

### Spacing

- `scale` defines the named tokens.
- `pairs: "contiguous"` generates adjacent pairs only.
- `pairs: false` disables default pairs.
- `customPairs` adds opt-in non-contiguous pairs.
- `utilities` extends or overrides the default utility map.
- Each `utilities` entry can be a single CSS property string or an array of CSS properties.
- `false` or `null` can be used to drop a built-in utility from the resolved map.

Example with custom pairs:

```js
export default {
  spacing: {
    pairs: "contiguous",
    customPairs: [
      "xs-lg",
      "sm-xl"
    ]
  }
};
```

## Design choices

- `:root` holds the runtime math and internal CSS variables
- `@utility` exposes the public API Tailwind v4 can consume naturally
- No blanket `@theme` export by default, to avoid polluting theme tokens unless that becomes a clear integration need
- No legacy fallback CSS or Tailwind JS plugin layer

## Programmatic API

```js
import { generateCss, generateCssFromFile, resolveConfig } from "@andreibratila/tailwind-utopia";
```

Also available:

```js
import { defineConfig } from "@andreibratila/tailwind-utopia";
```

## Credits

Inspired by Utopia and the earlier Tailwind Utopia plugin ecosystem.
