Ah yes what you’re saying is expose BEM classes only right? I agree that would fall apart with more complex components.
What I meant only affects the implementation. End users still use HEEX but the implementation wise you’d use BEM classes instead of utility classes (which are the reason currently !class is needed to override styles).
So implementation wise it would look something like this:
def button(assigns) do
assigns = assign(assigns, :class, fn -> ["button", button_intent_class(assigns.intent), @class] end)
~H"""
<button class={@class} {@extra}>
<%= render_slot(@inner_block) %>
</button>
"""
end
def button_intent_class("primary"), do: "button--primary"
def button_intent_class("success"), do: "button--success"
def button_intent_class("danger"), do: "button--danger"
And the corresponding CSS would be:
@layer components {
.button {
padding: theme('padding.2');
/* etc */
}
.button--primary {
background: theme('colors.green.500');
/* etc */
}
}
Then you could use the button as follows:
<.button intent="primary" class="p-3 text-red-500">Special button</.button>
Outputs:
<button class="button button--primary p-3 text-red-500">... etc ...
In Tailwind the components layer always comes before utilities so it has a lower specificity in CSS, so no need for !p-3 (important). Also think it would cleanup the petal codebase a bit but that is up to you two to decide of course.
I need to learn more about CSS vars - can you explain why that is better than defining the “primary” color in the tailwind config (theme.extend.colors.primary)?
I mentioned CSS vars mostly because when you separate the styling from the components you can reference CSS vars for additional customization. When all styles are inlined via utility classes this is not as easy.
You could also provide a default value for primary so that users don’t need to modify their tailwind.config.js for it to work. On the other hand you can also expose a Tailwind preset. So lot’s of options there ![]()






















