Basics
This check is disabled by default.
Learn how to enable it via .credo.exs.
This check has a base priority of normal and works with any version of Elixir.
Explanation
Enforces correct use of the cn(...) class-merging helper in HEEx
class={...} attributes alongside caller-provided assigns (@class,
@color, @size_class, …).
cn/1 (or whatever helper your project uses to wrap TwMerge.merge/1)
exists to deduplicate Tailwind classes when a parent overrides a child's
defaults — e.g. <.button class="w-full"> should win over the button's
built-in w-auto. That overhead is wasted when there's nothing to merge,
skipped (with broken overrides) when assigns are concatenated into a bare
list, and silently defeated when assigns aren't listed last inside
cn(...) — TwMerge resolves conflicts by keeping the last value.
Three rules are enforced on every HEEx class={...} attribute:
cn(...)requires an assign. A literalcn([...])call whose arguments don't contain any@assignis wasted — use a bare list (class={[...]}) instead.A bare list mixing literal classes with an assign requires
cn(...). A bare listclass={["a", "b", @color]}will not dedupe when@coloroverlaps with a sibling — wrap withcn([...])so TwMerge can merge.Inside
cn(...), assigns must come after all literal classes.cn([@color, "text-sm"])silently loses the@coloroverride because TwMerge keeps the later value — assigns must be listed last.
Solo assign (class={@class}, class={[@color]}) is fine — there's
nothing to merge.
The helper name is configurable via the :helper_name param
(default "cn").
Bad — cn without any assign
<div class={cn(["rounded border p-2"])}>...</div>Bad — assign mixed with siblings in a bare list
<div class={[
"flex w-full items-center",
"py-4 text-sm font-medium",
@class
]}>...</div>Bad — assign not last inside cn(...)
<div class={cn([@color, "rounded border p-2"])}>...</div>Good
<div class={["rounded border p-2"]}>...</div>
<div class={cn([
"flex w-full items-center",
"py-4 text-sm font-medium",
@class
])}>...</div>
<div class={@class}>...</div>
# An `@assign` used only as a condition in a nested expression is
# fine — the list elements resolve to literal strings, so there's
# no caller-provided class to merge.
<div class={[
"rounded border",
if(@compact, do: "p-1", else: "p-4")
]}>...</div>Check-Specific Parameters
There are no specific parameters for this check.
General Parameters
Like with all checks, general params can be applied.
Parameters can be configured via the .credo.exs config file.