Rbtz.CredoChecks.Design.CnInClassList (rbtz_credo_checks v0.3.0)

Copy Markdown View Source

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:

  1. cn(...) requires an assign. A literal cn([...]) call whose arguments don't contain any @assign is wasted — use a bare list (class={[...]}) instead.

  2. A bare list mixing literal classes with an assign requires cn(...). A bare list class={["a", "b", @color]} will not dedupe when @color overlaps with a sibling — wrap with cn([...]) so TwMerge can merge.

  3. Inside cn(...), assigns must come after all literal classes. cn([@color, "text-sm"]) silently loses the @color override 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.