Guide

Customizing

Every PhoenixPaper component takes a class attribute for your own Tailwind utilities. It adds to the component's built-in classes, it doesn't replace them. This page covers when that's enough, when you need Tailwind's ! modifier, and when an attr or paperize={false} is the better tool. For colors, see the Theming guide.

class adds, it doesn't replace

Your class is appended to the component's own classes, with no merging step. When yours and a built-in one set the same CSS property (two background colors, two flex directions), both end up on the element, and Tailwind decides the winner by the order the utilities appear in the generated stylesheet, not by the order you wrote them. So the override silently loses about half the time.

An elevated Card, three ways to give it the filled background

class="bg-…-highest"
Still surface-container-low.
class="!bg-…-highest"
Wins, but keeps the elevated shadow.
variant="filled"
MD3's filled card, no conflict.

The first one doesn't change: an elevated card already renders bg-pp-surface-container-low, which comes after -highest in Tailwind's stylesheet. The ! version wins every time, but only replaces the one property. The attr is the real fix: it switches the whole look, and nothing conflicts.

Show code
<%!-- No change: the elevated card's own bg-pp-surface-container-low wins --%>
<.pp_card class="bg-pp-surface-container-highest">...</.pp_card>

<%!-- Wins: ! makes it !important (the shadow stays) --%>
<.pp_card class="!bg-pp-surface-container-highest">...</.pp_card>

<%!-- Best: the attr, no conflict --%>
<.pp_card variant="filled">...</.pp_card>

Adding utilities: just use class

Anything the component doesn't already set is safe as plain class: layout and spacing around it (margins, width, max-width, grid placement), positioning, a cursor, a transition. Nothing to override, so nothing to lose.

Width and margin the components don't set

max-w-sm
A card capped at a width, with some top margin.
Show code
<%!-- Nothing built in to conflict with: plain class --%>
<.pp_button class="w-full">Full width</.pp_button>

<.pp_card class="mt-2 max-w-sm">
  <:title>max-w-sm</:title>
  A card capped at a width, with some top margin.
</.pp_card>

Replacing a built-in utility: the ! modifier

To change something the component already sets (its background, text color, padding, radius, display), prefix your utility with ! to make it !important. It then wins regardless of stylesheet order. Variants go in front of it: hover:!bg-rose-700, md:!px-8. Tailwind v4 also accepts the suffix form, bg-rose-600!; both work.

A filled Button, recolored

Use it for the property you're replacing, not everything: a stray ! also beats your own responsive and state variants later. If you're reaching for several on one component, an attr or paperize={false} is probably the better tool.

Show code
<%!-- Replacing the button's own background/text color: ! on each --%>
<.pp_button class="!bg-rose-600 !text-white hover:!bg-rose-700">
  Delete
</.pp_button>

<%!-- Same for any built-in utility, e.g. the outlined button's radius --%>
<.pp_button variant="outlined" class="!rounded-none">Square</.pp_button>

Reach for an attr first

Most of what people try to override through class has an attr that does it properly, works with dark mode and the theme tokens, and never conflicts. Check the component's options table before writing an override.

Instead of class=... Use
class="rounded-lg" on pp_button shape="square"
class="bg-pp-surface-container ..." on pp_card variant="filled"
class="bg-pp-secondary-container ..." on pp_button variant="tonal"
class="border ..." on pp_button variant="outlined"
class="text-xs px-2" on pp_button size="xs"
class="fixed inset-0" on pp_dialog variant="fullscreen"
class="size-4" on pp_icon size="sm"
class="fixed bottom-6 right-6" on pp_fab position="fixed" class="bottom-6 right-6"
class="text-pp-on-primary" on a button in a colored bar color="inherit"

paperize={false}: start from scratch

When you want a component's behavior but none of its look, pass paperize={false}. Every built-in class is dropped and only your class renders, so there's nothing to override and no ! needed. The structure the component needs to work (a checkbox's hidden input, a link vs. button root, ARIA attributes) stays.

Same component, paperize on and off

Show code
<.pp_button
  paperize={false}
  class="rounded-md border-2 border-dashed px-3 py-1 font-mono text-sm"
>
  Fully custom
</.pp_button>

Styling inside a component

class lands on the component's root element. For the parts inside it, you have three options, in order of preference.

A dedicated attr, where the component has one pp_menu's trigger_class, for the element that opens it
Your own markup in a slot Slot content is yours: style it directly, or wrap it in an element you style
An arbitrary child variant on the root [&_svg]:size-4 targets descendants; add ! if it collides with a built-in
Show code
<%!-- 1. A dedicated attr --%>
<.pp_menu id="account-menu" trigger_class="ms-auto">
  ...
</.pp_menu>

<%!-- 2. Your own markup in a slot --%>
<.pp_card>
  <:title><span class="text-pp-primary">Billing</span></:title>
  ...
</.pp_card>

<%!-- 3. A child variant on the root (! if it collides) --%>
<.pp_button variant="outlined" class="[&_svg]:!size-4">
  <:start_icon><.pp_icon name="hero-trash" /></:start_icon>
  Delete
</.pp_button>

Site-wide tweaks in app.css

To change a component everywhere at once, target its marker instead of editing every call site: each component renders a data-pp-component attribute (button, card, list-item, ...). Put the rule after the phoenix_paper import in app.css. For colors, change the --color-pp-* tokens instead (see Theming): they reach every component, in light and dark.

Show code
/* assets/css/app.css, after the phoenix_paper import */

/* Every card: a thin outline */
[data-pp-component="card"] {
  border: 1px solid color-mix(in srgb, var(--color-pp-outline) 20%, transparent);
}

/* Every button: no uppercase-ish tracking */
[data-pp-component="button"] {
  letter-spacing: normal;
}

Checklist

Before you write an override:

Is there an attr for it? (color, variant, size, shape, position, ...)
Is it a color? Change the --color-pp-* token in app.css instead.
Does the component already set this property? If not, plain class is enough.
If it does, prefix only that utility with ! (variants first: hover:!bg-...).
Overriding most of the look? Use paperize={false} and style it yourself.
Same tweak everywhere? One [data-pp-component=...] rule in app.css.

z7ealth 2026