Why your widget looks unstyled
When you paste a component from your Tailwind project into RuleCMS, the markup still carries the class names — rounded-xl, px-4, bg-brand-500. The CSS behind those names does not come with them. In your project, Tailwind scanned your source at build time and wrote that CSS. RuleCMS does not run that build over widget content, so a class sitting on a column has nothing that turns it into a declaration.
Generate Tailwind CSS on this widget's CSS Settings page closes that gap. It reads the class names the widget and its child collections already use, optionally takes your design tokens, compiles the pair with Tailwind itself, and writes the result into this widget's CSS catalog.
What this feature does
Generation walks the widget tree, compiles each class name it finds, and merges the CSS into four catalog slices this widget already has:
- Class definitions — the declaration block for each utility, including variants such as
hover:andmd:. - CSS variables — only the theme tokens those classes actually reference, such as
--spacingor--color-brand. - Keyframes — named frames for stock
animate-*utilities. - CSS properties — the
@propertyregistrations shadows, rings, transforms, and filters need so they compose.
Classes you already wrote by hand are kept. Generated utilities are appended after them. Publish flags are not rewritten — generation decides what CSS exists, not whether the embed includes it.
Why some things cannot work
The limits below are architectural, not missing polish. They exist because a widget is a guest on someone else's page, and because RuleCMS cannot run code you paste.
| Constraint | What that means for you |
|---|---|
| A widget cannot reset the host page | Tailwind Preflight — the base layer that zeros margins and sets box-sizing — is never applied. If your component depended on those resets, add them yourself under Widget chrome. |
| We never run your JavaScript | A pasted tailwind.config.js is read as text, not executed. Plugins, require(), spreads, and function calls cannot run. That is why plugin classes such as prose never generate. |
| Class names have a character allowlist | Names cannot contain !, #, =, @, *, &, >, or parentheses. Square brackets are allowed, so w-[42px] works and bg-[#1a4f8a] does not. |
| Only class names written in the tree are seen | A class computed at runtime — bg-${color}-500 — is invisible, the same way Tailwind's own scanner would miss it. The name has to appear literally on the widget or a child collection. |
| This is a snapshot | Generation writes CSS this widget owns. It is not connected to your repository. Re-run it when you add classes or change the theme. |
How to use it
- Open the widget's CSS Settings page.
- If you have unsaved edits on a catalog form, save or discard them first. The button is disabled while a form is dirty so generation cannot overwrite work you have not stored.
- Click Generate Tailwind CSS. A dialog opens and starts analysing the widget. Nothing is written until you click Generate.
- Read the preview counts — classes found, and how many class definitions, CSS variables, and CSS properties would be added. The skipped list is the only place a class that produced nothing is reported.
- Optionally paste your theme into Paste your Tailwind theme or config. If the component uses your own colour or spacing names, this step is what makes those classes generate. Leaving it blank is valid — generation then uses Tailwind's defaults plus any variables already on this widget. After you paste, click Analyze with this theme so the preview includes those tokens.
- Click Generate. The button stays disabled while analysis is running, and if the preview would write nothing. The dialog locks while it writes, then shows whether generation succeeded. Click OK on success, then publish the widget. Live embeds do not change until you publish.
The information icon next to the button and the dialog title opens this page in a new tab, so you can keep the dialog open while you read.
What to paste
You do not have to tell us whether the project is Tailwind v3 or v4. Paste either of the following and we detect the format. Leave the box blank if you have no custom theme or config.
Tailwind v4 — the @theme block in your CSS file
Usually app/globals.css or src/index.css, next to @import "tailwindcss". You can paste the whole stylesheet; only @theme declarations are read. A bare list of --token: value lines also works.
@theme {
--color-brand: #6d28d9;
--radius-card: 12px;
}Tailwind v3 — your tailwind.config.js
In your project root. Paste the whole file, or just the theme object. Nested keys flatten: colors.brand becomes --color-brand, which is what lets bg-brand compile.
module.exports = {
theme: {
extend: {
colors: { brand: '#6d28d9' },
borderRadius: { card: '12px' },
},
},
};require(), a variable, a function call, or a spread cannot be resolved. Those tokens are listed after you generate under "Tokens we could not read." Paste the resolved values, or paste the v4 @theme block if you have one.After the first successful generate, the tokens that were actually used are stored on this widget. A later run can reuse them without another paste. Only used tokens are stored — paste seven colours and use three, and three persist. Start using a fourth later and paste again, or put the full set on CSS Variables yourself so they all stay.
What we support
These compile when the class name is stored on the widget and, for design-system names, when the matching token is available.
| Category | Examples |
|---|---|
| Plain utilities | flex z-50 w-full truncate sr-only |
| Theme-backed spacing, type, radius | p-4 rounded-xl text-base font-sans |
| Fractions, negatives, px | w-1/2 -mt-4 mt-px |
| Alpha modifiers | text-red-500/50 ring-brand/30 |
| Breakpoints | sm: md: lg: xl: 2xl: max-md: min-[600px]: |
| Pseudo-classes and pseudo-elements | hover: focus: focus-visible: before: after: placeholder: |
| Relational variants | group-hover: peer-checked: — the group / peer marker classes themselves generate nothing; leave them on the markup. See Silent surprises. |
| Media, direction, ARIA | dark: motion-reduce: print: rtl: aria-expanded: |
| Stacked variants | dark:hover: sm:focus-visible: |
| Composable utilities | shadow-sm ring-1 blur-sm scale-95 from-red-500 |
| Arbitrary values with safe characters | w-[42px] has-[:checked]:flex supports-[display:grid]:flex |
Design-system classes such as bg-brand-500 work only when the matching token is on this widget or in the paste. Without that token they look exactly like a typo.
What we do not support
| This class | Why it is skipped | What to do |
|---|---|---|
bg-[#1a4f8a] | # is not allowed in a class name | Add --color-brand: #1a4f8a to the theme and use bg-brand |
data-[state=open]:flex | = is not allowed | Define the rule under Class Definitions |
!flex | ! is not allowed | Write the declaration with !important by hand, or rely on catalog order — generated utilities already win over earlier hand-written classes |
@md:flex, @container | @ is not allowed; container queries drop out | Use a breakpoint variant such as md:flex, or write the container query under Class Definitions |
*:p-4, [&>*]:p-4 | *, &, and > are not allowed | Put p-4 on the children, or write the child selector under Class Definitions |
bg-(--my-color) | Parentheses are not allowed | Register the colour as a theme token and use bg-my-color |
prose, other plugin classes | Plugins never run | Copy the plugin CSS into Class Definitions |
Your own btn from @utility or @apply | Only @theme tokens are read from a paste | Define btn under Class Definitions |
| A class built in JavaScript at runtime | Nothing in the tree to find | Put the full class name on the component as a literal string |
Silent surprises
Most failures appear in the skipped list. These three do not, or they appear in a way that is easy to misread.
Class-based dark mode follows the operating system
Generated dark: classes use @media (prefers-color-scheme: dark). If your project redefined the variant — Tailwind v4 @custom-variant dark (&:where(.dark, .dark *)), or v3 darkMode: 'class' — that redefinition is dropped. The class is generated, the CSS is valid, and nothing is reported as skipped. It just responds to the OS setting instead of a .dark ancestor.
If your site has a dark-mode toggle, check it by hand after you generate. To keep the class-based trigger, add a theme under Themes that sets the tokens under .dark, or rewrite the dark: rules under Class Definitions.
group and peer in the skipped list are expected
They are marker classes. They carry no CSS, so generation reports them as "Not a Tailwind utility." The variants that depend on them — group-hover:flex, peer-checked:hidden — do generate, and they match the literal class in the markup. Do not remove group or peer from the component because the report mentioned them.
A token that overrides a Tailwind default retunes every utility
If your paste (or this widget's variables) sets --spacing: 8px, then p-4 becomes 32px instead of 16px. That is what your project does, so it is correct — and it is listed in the preview under "Overrides a Tailwind default" so you can see the reach before you confirm.
Workarounds
| What you wanted | Do this instead |
|---|---|
| A hex colour in the class name | Move the colour into the theme as --color-* and use bg-* / text-* |
| Plugin output such as Typography | Paste the compiled plugin CSS into Class Definitions |
A custom @utility or @apply class | Recreate it under Class Definitions. Generation will not delete a hand-written class on the next run |
| Class-based dark mode | Use Themes to override tokens under .dark, or rewrite the generated dark: bodies |
| Resets you used to get from Preflight | Add them on Widget chrome |
| A class only assembled in code | Write the full name on the node so generation can see it |
| A computed value in a v3 config | Paste the resolved literal, or paste the v4 @theme block |
After you generate
- Review the new entries on Class Definitions and CSS Variables. You can edit any of them; the next generate overwrites a generated name and leaves names that exist only on the widget.
- Publish the widget. Composer and Preview already read the draft; the live embed does not change until you publish.
- Generate again after you add class names or change the theme. The second run does not need a paste if the tokens are already on this widget.
Related guides
- Class Definitions — hand-write anything generation cannot store
- CSS Variables — keep the full token set on the widget so later generates do not need another paste
- Themes — host dark-mode strategy when
dark:should not follow the OS - Widget chrome — resets you no longer get from Preflight
- Publishing — draft versus live
- Widgets — CSS Settings and the rest of the widget