# Frivolous Concatenation

import { Shortcut } from "../src/lib/Shortcut";

I see this pattern in a lot of Tailwind code.

```tsx twoslash
import { cn } from "./src/code-blocks/frivolous-concatenation.js";
// prettier-ignore
<div
// ---cut---
className={cn(
  "flex items-center gap-1.5",
  "px-4 py-2.5",
  "typography-body-sm text-neu-700 dark:text-neu-600 whitespace-pre",
)}
// ---cut-after---
/>;
```

Please don't do this.

I get it. Human minds yearn for order. The Soulless imitate our weakness.

Just write it together. One string.

```tsx twoslash wrap
import { cn } from "./src/code-blocks/frivolous-concatenation.js";
// prettier-ignore
<div
// ---cut---
className="typography-body-sm flex items-center gap-1.5 whitespace-pre px-4 py-2.5 text-neu-700 dark:text-neu-600"
// ---cut-after---
/>;
```

I understand the motivation to split it, especially in more complex production
scenarios, like in `shadcn`:

```tsx line-numbers=293 twoslash github="https://github.com/shadcn-ui/ui/blob/ac306c60f569c342b6e0b3586867efd7a7658766/apps/v4/registry/new-york-v4/ui/sidebar.tsx#L293-L300"
import { cn } from "./src/code-blocks/frivolous-concatenation.js";
declare const className: string;
// prettier-ignore
<div
// ---cut---
className={cn(
  "hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
  "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
  "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
  "hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
  "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
  "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
  className,
)}
// ---cut-after---
/>;
```

<aside>
  if you've never seen a `cn` function, you can read it as _classNames_ or
  _conditional concat_.
</aside>

There is some readability gain, but it's not worth the things we lose. I'm not
even talking about the performance cost. Even if we use the most basic `cn`
function like `xs.filter(Boolean).join(' ')`, no `tailwind-merge`. We lose
predictability and greppability.

**Predictability:** The
[Prettier plugin for Tailwind CSS](https://github.com/tailwindlabs/prettier-plugin-tailwindcss)
can order classes in long strings, so we always get the same order. Just trust
the formatter. Predictable is nice.

**Greppability:** If there's no **concatenation**, even for mapping from props
to style, we can copy a class from the browser and immediately jump to the code.

<aside>
  [Greppability is still
  underrated](https://morizbuesing.com/blog/greppability-code-metric/).
</aside>

I always rebind project search, so it's just:

<div
  className="flex items-center gap-1"
  style={{
    marginTop: "-1.75rem",
  }}
>
  {["cmd+c", "alt+tab", "cmd+e", "cmd+v"].map((shortcut, i) => (
    <>
      {i !== 0 && <span className="translate-y-px opacity-60">→</span>}
      <Shortcut shortcut={shortcut} />
    </>
  ))}
</div>

And I'm right where I wanna be.

## But what if...

But what if the concatenation isn't needless? If we actually have some state or
props, such as `variant` or `size`, that we're mapping to styles.

```tsx line-numbers=231 twoslash github="https://github.com/shadcn-ui/ui/blob/ac306c60f569c342b6e0b3586867efd7a7658766/apps/v4/registry/new-york-v4/ui/sidebar.tsx#L231-L241"
import { cn } from "./src/code-blocks/frivolous-concatenation.js";
declare const className: string;
declare const variant: "sidebar" | "floating" | "inset";
declare const side: "left" | "right";
// prettier-ignore
<div
// ---cut---
className={cn(
  "w-(--sidebar-width) fixed inset-y-0 z-10 hidden h-svh transition-[left,right,width] duration-200 ease-linear md:flex",
  side === "left"
    ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
    : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
  // Adjust the padding for floating and inset variants.
  variant === "floating" || variant === "inset"
    ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
    : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
  className,
)}
// ---cut-after---
/>;
```

I'm not a radical here. I like using `aria-` attributes for state and I've used
`data-` attributes for props like `variant`.

```tsx wrap github="https://github.com/graphql/graphql.github.io/blob/82ac0f2c1c53f0d50a6755a63eb19afbdc22a77c/src/app/conf/_design-system/button.tsx#L61"
"relative flex items-center justify-center gap-2.5 font-normal text-base/none text-neu-0 bg-neu-900 hover:bg-neu-800 active:bg-neu-700 font-sans h-14 px-8 data-[size=md]:h-12 data-[variant=secondary]:bg-neu-100 dark:data-[variant=secondary]:bg-neu-100/80 dark:data-[variant=secondary]:hover:bg-neu-200/50 data-[variant=secondary]:text-neu-900 data-[variant=secondary]:hover:bg-neu-200/75 data-[variant=secondary]:active:bg-neu-200/90 data-[variant=tertiary]:bg-neu-100 data-[variant=tertiary]:text-neu-900 data-[variant=tertiary]:hover:bg-neu-200 data-[variant=tertiary]:active:bg-neu-300 gql-focus-visible [aria-disabled]:bg-neu-800 aria-disabled:pointer-events-none dark:data-[variant=tertiary]:bg-neu-900/10 dark:data-[variant=tertiary]:text-neu-900 dark:data-[variant=tertiary]:hover:bg-neu-900/[.125] dark:data-[variant=tertiary]:active:bg-neu-800/20 dark:data-[variant=tertiary]:ring-1 dark:data-[variant=tertiary]:ring-inset dark:data-[variant=tertiary]:ring-neu-900/20",
```

In the code above, I obvi started with two variants, and only later needed a
tertiary. I&nbsp;can acknowledge that this is getting to the point where I
should use [`cva`](https://github.com/joe-bell/cva). We don't really need the
greppability on the class either, because looking at the component, you can
guess it's called Button.

Still, it's _fine_. It's not even close to my ugliest Tailwind code. I'm not
gonna refactor it until it grows to the point of being unbearable.

We still put too much focus on readability and not enough on perf, ease of
finding, and ease of working with the code.

Instead, we can embrace ugly and leverage the tools we have to make it more
bearable.

Augment yourself. We live in Cyberpunk, right?

Ask the clanker to parse it if you really can't look at it. Set
`"editor.wordWrap": "on"` or equivalent in your text editor, grab some
extensions like
[Tailwind Fold](https://marketplace.visualstudio.com/items?itemName=stivo.tailwind-fold)
or
[Tailwind CSS Highlight](https://marketplace.visualstudio.com/items?itemName=ellreka.tailwindcss-highlight).

Don't change the runtime just for something as subjective as readability or
aesthetics.

Don't go overboard and write everything in one huge string always: `cn` is a
great function. `class-variance-authority` is amazing. We should respect them
and not reach for them instantly and everywhere.

The AI reviewing this wants me to add a rule of thumb, something constructive to
finish the post with.

Nah. Trust your gut. And git gud, man up or woman up, or both.

Also don't panic, we've been frivolous and sloppy for a long time. Phil Pearl
wrote
[a parallel post](https://medium.com/swlh/bad-go-frivolous-sprintf-2ad28fedf1a0)
about needless calls of Golang `fmt.Sprintf` in 2019. \
There's a pattern.
