> ## Documentation Index
> Fetch the complete documentation index at: https://cactal.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Style a website

> Use global CSS and Tailwind CSS, define theme tokens and animations, and understand class discovery and stylesheet build constraints.

Every new website starts with a `global_css` source file containing `@import "tailwindcss";`. Tailwind CSS 4 is already installed and integrated into Cactal's website compiler, so there is no package installation or JavaScript configuration. Keep the import for Tailwind utilities and preflight, or remove it to use plain CSS without Tailwind's preflight.

## Add global CSS

The `global_css` singleton contains raw site-wide CSS:

```css global_css theme={null}
@import "tailwindcss";

:root {
	--brand: #22683b;
	--surface: #f6f8f4;
}

html {
	background: var(--surface);
	color: #172019;
}

.prose-card {
	border: 1px solid color-mix(in srgb, var(--brand) 24%, transparent);
	border-radius: 1rem;
	padding: 1.5rem;
}
```

The framework compiles, optimizes, prefixes, and minifies this CSS during the website build, then embeds the result in the document `<head>`. Modern CSS nesting, custom properties, media queries, keyframes, and standard at-rules are supported.

Upsert it like any other singleton:

```bash Set global CSS theme={null}
curl -X PUT 'https://api.cactal.ai/v1/websiteSourceCode/files' \
  -H "Authorization: Bearer $CACTAL_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "websiteId": "V1StGXR8_Z5jdHi6B-myT",
    "file": { "type": "global_css" },
    "content": "@import \"tailwindcss\";\n\n:root { --brand: #22683b; }\nbody { color: var(--brand); }",
    "leaseToken": "exampleLeaseToken00000000000000000000000000"
  }'
```

Global CSS is source-versioned. It reaches the draft after the preview build and reaches production only after publish.

## Enable or disable Tailwind CSS

New sites are already enabled by the visible Tailwind import at the top of `global_css`:

```css global_css theme={null}
@import "tailwindcss";
```

Then use utilities directly in page and component TSX:

```tsx pages/home theme={null}
import { definePage } from '@website'

export default definePage({
	route: '/',
	render: () => (
		<main className="mx-auto grid min-h-screen max-w-6xl place-items-center px-6">
			<section className="rounded-3xl bg-emerald-950 p-10 text-white shadow-xl">
				<h1 className="text-4xl font-semibold tracking-tight">
					Build with Cactal
				</h1>
			</section>
		</main>
	)
})
```

The import is the only setup step. Remove that line to use plain CSS without Tailwind's preflight. Without it, Tailwind-looking class names do not generate utilities, while ordinary authored CSS still builds normally.

### Class discovery

At build time, Cactal scans the website's TSX source, including pages, components, and the custom not-found page. It recognizes complete class candidates in source strings, including variants, arbitrary values, important modifiers, and negative utilities.

```tsx theme={null}
<div className="grid hover:bg-blue-500 md:flex !p-4 -mt-4 w-[400px]" />
```

Do not construct partial class names dynamically:

```tsx theme={null}
// Invalid for class discovery: the complete classes do not appear in source.
<div className={`bg-${tone}-500`} />
```

Select complete literals instead:

```tsx theme={null}
const toneClass = tone === 'success' ? 'bg-emerald-500' : 'bg-amber-500'

return <div className={toneClass} />
```

Class candidates inside installed npm package source are not scanned. If a package emits class names at runtime, ensure every required complete class also appears in your website source or recreate the required styles in `global_css`.

## Define a theme

Tailwind CSS 4 theme variables work directly in `global_css`:

```css global_css theme={null}
@import "tailwindcss";

@theme {
	--color-brand-50: oklch(0.97 0.02 150);
	--color-brand-500: oklch(0.58 0.16 150);
	--color-brand-900: oklch(0.28 0.08 150);
	--font-display: "Newsreader", ui-serif, serif;
	--animate-fade-in: fade-in 400ms ease-out both;
}

@keyframes fade-in {
	from {
		opacity: 0;
		transform: translateY(0.5rem);
	}
	to {
		opacity: 1;
		transform: translateY(0);
	}
}
```

Those variables generate utilities such as `bg-brand-500`, `font-display`, and `animate-fade-in` when the utilities appear in scanned source.

You can combine Tailwind layers with ordinary selectors:

```css theme={null}
@import "tailwindcss";

@layer base {
	body {
		font-family: var(--font-sans);
	}
}

@layer components {
	.marketing-link {
		text-decoration-thickness: 0.08em;
		text-underline-offset: 0.2em;
	}
}
```

## Use assets and fonts

Upload a font as a `file` asset. Begin the upload with the font's canonical MIME type and exact byte size:

```bash Begin the font upload theme={null}
curl -X POST 'https://api.cactal.ai/v1/websiteAssets' \
  -H "Authorization: Bearer $CACTAL_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "websiteId": "V1StGXR8_Z5jdHi6B-myT",
    "kind": "file",
    "filename": "acme-sans.woff2",
    "mimeType": "font/woff2",
    "byteSize": 42816
  }'
```

Use the returned `upload.url`, method, and headers to send the raw bytes, then finalize the returned asset id:

```bash Upload and finalize theme={null}
curl -X PUT "$UPLOAD_URL" \
  -H 'Content-Type: font/woff2' \
  -H 'Content-Length: 42816' \
  -H 'Cache-Control: public, max-age=31536000, immutable' \
  --data-binary @acme-sans.woff2

curl -X POST 'https://api.cactal.ai/v1/websiteAssets/mP4qR6sT8uV0wX2yZ3aB5/finalize' \
  -H "Authorization: Bearer $CACTAL_API_KEY"
```

Finalize returns the stable CDN URL:

```json theme={null}
{
  "id": "mP4qR6sT8uV0wX2yZ3aB5",
  "kind": "file",
  "filename": "acme-sans.woff2",
  "mimeType": "font/woff2",
  "byteSize": 42816,
  "width": null,
  "height": null,
  "url": "https://cdn.cactal.app/website-assets/mP4qR6sT8uV0wX2yZ3aB5.woff2",
  "createdAt": "2026-08-06T15:06:00.000Z"
}
```

Use that ready asset URL in `@font-face` or `url(...)`:

```css theme={null}
@font-face {
	font-family: "Acme Sans";
	src: url("https://cdn.cactal.app/website-assets/mP4qR6sT8uV0wX2yZ3aB5.woff2") format("woff2");
	font-display: swap;
}

.hero {
	background-image: url("https://cdn.cactal.app/website-assets/aB3xK9mQpR7sTn2vWc5Yd.webp");
}
```

WOFF2, WOFF, TTF, OTF, and TTC files are supported. Font uploads declared as `application/octet-stream` or with a legacy font MIME alias are stored with their canonical font MIME type. Fetch the current ready URL with `GET /v1/websiteAssets/{assetId}`. See [Upload assets](/docs/guides/upload-assets) for the full upload flow.

Prefer absolute HTTPS or root-relative URLs. Because Cactal embeds site CSS in every page, a relative URL such as `images/hero.png` resolves relative to the requested page path and can break on nested routes.

## Imports and plugins

`@import "tailwindcss"` is the only CSS import the compiler resolves. Local file imports, package CSS imports, and other import specifiers fail the framework quality gate.

Remote stylesheet imports such as `@import url("https://fonts.googleapis.com/css2?family=Fraunces")` are not resolved at build time and do not fail the gate. Placed above `@import "tailwindcss"`, they pass through to the compiled stylesheet and the browser loads them at runtime. Placed after it, they are silently removed from the compiled output because the Tailwind import expands into regular rules and CSS requires every `@import` to precede other rules. For web fonts, prefer preconnect and stylesheet `<link>` tags in `custom_head_start` (the browser discovers them earlier than a CSS import) or a self-hosted `@font-face` as shown above. See [Customize site-wide files](/docs/guides/customize-site-wide-files) for the head injection points.

Tailwind JavaScript configuration files and `@plugin` modules are not part of the website source model. Use CSS-first Tailwind configuration such as `@theme`, authored CSS, and keyframes in `global_css`.

<Warning>
  Do not paste untrusted CSS. Global CSS can load remote resources, cover interactive content, or change the entire site's behavior and appearance.
</Warning>

## Build behavior

The website build:

1. Scans website TSX source for Tailwind candidates.
2. Compiles `global_css`, loading Tailwind only when requested.
3. Generates utilities for discovered candidates.
4. Optimizes, prefixes, and minifies the final stylesheet.
5. Embeds the stylesheet before `custom_head_end` content.

Invalid CSS or an unsupported import fails `head/check` and publish. The previous published deployment remains live when a new build fails.

## Troubleshooting

| Problem                                            | Cause                                                                    | Fix                                                                |
| -------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------ |
| Tailwind classes have no effect                    | `global_css` does not import Tailwind                                    | Add `@import "tailwindcss";`                                       |
| One dynamic color or spacing class is missing      | The class was assembled from string fragments                            | Put each complete class literal in source                          |
| A package component is unstyled                    | Its runtime classes were not scanned or it expects a separate stylesheet | Expose complete classes in website source or use authored CSS      |
| `Global CSS imports ... but only ... is supported` | The stylesheet imports something other than Tailwind                     | Remove the import and place supported CSS directly in `global_css` |
| A nested-page background image is missing          | Its URL is relative to the request path                                  | Use an absolute HTTPS or root-relative URL                         |
| Production still shows old styles                  | The CSS change is only in head                                           | Check the draft, run `head/check`, and publish                     |

## Next steps

* [Build pages with CMS data](/docs/guides/build-pages-with-cms-data) for the page and render API
* [Customize site-wide files](/docs/guides/customize-site-wide-files) for custom HTML slots and the not-found page
* [Edit source code](/docs/guides/edit-source-code) for leases and singleton upserts
* [Preview drafts and versions](/docs/guides/preview-drafts-and-versions) before publishing visual changes
