Utility classes are 2022 - now we have utility elements

is this too evil? :sunglasses:

<.flex gap-4 justify-start>
  ...
</.flex>

<.grid grid-cols-3 gap-3>
...
</.grid>
defmodule Components.Tailwind do
  use Phoenix.Component
  import Phoenix.LiveView.HTMLEngine, only: [component: 3]

  def flex(assigns), do: component(&div/1, classes(assigns, "flex"), [])
  def grid(assigns), do: component(&div/1, classes(assigns, "grid"), [])
  ...

  defp div(assigns) do
    ~H"""
    <div class={@classes}>
      <%= render_slot(@inner_block) %>
    </div>
    """
  end

  defp classes(assigns, base) do
    classes =
      assigns
      |> Enum.reject(fn {k, v} -> k == :__changed__ || v != true end)
      |> Enum.map(fn {k, _} -> k end)
      |> Enum.join(" ")

    assign_new(assigns, :classes, fn -> base <> " " <> classes end)
  end
end

Canā€™t say I havenā€™t thought of it more than a few times :upside_down_face:

I was thinking along the lines of more of a ā€œstyled componentsā€ approach like grid="cols-2 justify-end gap-2" and so on, but ultimately decided ā€œnahā€. But then againā€¦ maybe.

EDIT

Oh wait, I missed <.grid which yes, thatā€™s far more of a styled components approach.

Please also create SubSubSubSubHeader :slight_smile:

Evil or not; it looks in the spirit of Tailwind :slight_smile:

1 Like

The company I used to work for does have a design system in React that is very close, though a lot of the ā€œvaluesā€ took their own values (like for example). Itā€™s based off of styled system.

I think accepting any bare attribute as a class is pretty iffy. I was thinking this could better suited to more of explicitly defining what attributes are style attributes. A few special ones could have their own components too, like in your example:

<.flex justify="center" items="center" gap={4} />
<.img rendering="pixelated" />

ā€¦and then of course youā€™d have to have <.box> or something for <div>s. I guess really you would need a componentized version of most or maybe all dom elements (though slots could get rid of a bunch of them probably).

Anyway, then the values could just map to tailwind classes, or possibly even another CSS frameworkā€™s classes.

Anyway, Iā€™m just thinking out loud as I have thought quite a bit about it in the past but itā€™s been a while, so sorry if Iā€™m not being clear or even making any sense.

Iā€™d suggest taking a step back and ask yourself if changing the syntax for how to apply css is actually useful. In my opinion thereā€™s not much use in that. Iā€™d rather look into actual higher levels of abstraction, like e.g. presented on https://every-layout.dev/.

Also Iā€™d worry thereā€™s pitfalls for LVs change tracking with the approach youā€™re taking there.

4 Likes

Do not quite get what the difference to class="cols-2 justify-end gap-2" is hereā€¦?

that looks funky!

Yeah though about that. But Iā€™d need way more experience to find right attributes, in my brutal approach there is no need for this.

The main reason I did that in the first place was because I can. But then I noticed that I like it.
It brings a little order in the tailwind stew (loving tailwind! but stillā€¦) without losing any of its flexibility. (For more involved classes that are not valid attribute-names one could add an explicit class attr).

Will do.

I was bitten by that some time ago and thought I understood whats going on, canā€™t see what would be the problem hereā€¦!?

I basically just mis-remembered and responded too quickly and tried to cover it up :upside_down_face: I meant the <.grid cols={2} ... /> approach.

import Phoenix.LiveView.HTMLEngine, only: [component: 3]

Maybe not for change tracking, but afaik using this essentially means the LV has no means of splitting the static content of a template from the dynamic parts, which is used to optimize diffs by not sending the static parts again.

1 Like

I think the crux of why I didnā€™t dive into even really trying this is because we shouldnā€™t really be repeating ourselves with Tailwind. The ā€œmessinessā€ of Tailwind is usually relegated to single components (and can be organized by type over multiple lines). I try to have as few Tailwind classes as I can in LiveViews or dead view files, so getting rid of the class attribute didnā€™t feel like a giant win.

1 Like

Thinking about this some more I might actually be wrong here. This static vs dynamic part is likely done at runtime, where using the a dynamic component wouldnā€™t really be different to a compile time known one.

1 Like