gg_cn
gg_cn — a pure-Gleam tailwind-merge.
Ported from cnfast’s logic engine
(itself a faithful reimplementation of tailwind-merge v4), this resolves
conflicting Tailwind utility classes by keeping the last one per conflict
group: tw_merge("px-2 px-4") == "px-4".
This is the engine behind gg_base_ui/helpers/cn (which gg_ui and its
components use): a real clsx + tailwind-merge, for any markup that mixes
raw Tailwind utilities and needs conflict resolution. It is pure Gleam (no
FFI), so it compiles and behaves identically on JS and the BEAM — which is
why it can back gg_ui’s cn without bringing Elixir tails into the build.
Usage
import gg_cn
let merge = gg_cn.new()
merge |> gg_cn.tw_merge("px-2 py-1 px-4") // "py-1 px-4"
new() builds the class trie once (it is moderately expensive); reuse the
returned Merger across calls rather than rebuilding it per merge. For
render-time callers, prefer default — a process-global Merger
built once and reused (what gg_base_ui/helpers/cn uses).
Types
A class value, mirroring clsx/tailwind-merge’s accepted inputs: a string,
a conditional (a Bool gate paired with a class string), or a nested list.
pub type ClassValue {
Class(String)
When(Bool, String)
Group(List(ClassValue))
}
Constructors
-
Class(String) -
When(Bool, String)When(condition, classes)contributesclassesonly whencondition. -
Group(List(ClassValue))
Values
pub fn cn(merger: Merger, values: List(ClassValue)) -> String
clsx + twMerge in one — the shadcn cn helper. Joins the class values,
then resolves conflicts.
pub fn default() -> Merger
A process-global default Merger, built once on first use and reused
forever after. Backed by global_value (persistent_term on the BEAM, a
singleton object on JS), so the expensive trie + regex build happens a single
time per runtime — the right thing for render-time callers (e.g. gg_ui’s
cn) that shouldn’t thread a Merger around or rebuild it per call.
This memoizes only the engine (the trie). It does not yet cache per-input merge results; that LRU is a separate, optional layer (see the package README / gg_ui follow-up).
pub fn tw_join(values: List(ClassValue)) -> String
Join class values into one space-separated string, dropping falsy/empty
parts — the twJoin/clsx step, without conflict resolution.
pub fn tw_merge(merger: Merger, class_list: String) -> String
Merge an already-joined, space-separated class string, resolving conflicts.
The result is memoized by input string (JS only; the BEAM recomputes — see
internal/cache). Output depends solely on the input (single baked config),
so the cache never changes results, only speed.