LV and toggle tailwind css classes vs specificity

say you have a dom elm that has a class of mt-2 and you want to nullify it to say mt-0 conditionally.

Well something like this maybe what you end up with.

class={["mt-2", @class]}

where @class will have your mt-0 assigned from the attrs.

The problem with this approach is it assumes mt-0 has more specificity than mt-2. but the issue is there is no specificity in this context. Maybe if you had hover:mt-0 but still I think this pattern is error prone. This is kind of how the generators position you or at least I think frames the solution to this problem.

What are people doing to be more explicit?
for example I’ve started to explicitly remove and add classes not just try to override via specificity.
But that does not look nice in my current pattern.
IE:

class={[
          toggle_class(@errors),
          "rounded ring-0 ring-inset border-1 focus:ring-0 w-full",
          @class
        ]}

where toggle_class is explicitly adding and removing classes. TBH I wish there was better way, not that this is horrible but as if there could be a possible better way thats more broken out of this class attrs assigns.

Maybe if there was a hook/callback that fired before the assigns of the class attr where I could hijack the assigns.

I try and design components so that they don’t need any class overrides or, if they do, I provide a known set of options—like perhaps <.some_thing tight /> would remove the margins. Although when it comes to margins, I generally keep them off of most components and use wrappers to set them. I do put margins on “container” components. A common component I make is section or article (this is a paired down version):

attr :title, :string

slot :inner_body

def section(assigns) do
  ~H"""
  <section class="mb-4 last:mb-0">
    <header class="p-2>
      <h1 class="text-5xl font-semibold><%= @title %></h1>
    </header>

    <div class="p-2">
      <%= render_slot(@inner_block) %>
    </div>
  </section>
  """
end

This way the spacing is even between sections but there is no margin one the last one—which in the case of just one section means there are no margins. This allows me to keep margins consistent between sections, but control the out margins in different contexts:

  ~H"""
  <div class="mb-24">
    <.section title="Some cool section about cool stuff">
      <p>I like stuff.</p>
    </.section>
  </div>

  <p>Some text that needs its space</p>
  """

I do take a lot of margins off of the generated CoreComponents.

PS, sorry that that isn’t a very direct answer to your question, I was zeroing in on needing to change margins and this is how I handle that.

No worries at all, I’m just trying to get people talking about this cause I’ve not see a lot of opinions on it yet.
That said I am more interested in the generic pattern of what that should look like by default, given the fact that tailwind does not have specificity, things like this I wonder if they are giving bad advice?

class={[
          input_border(@errors),
          "some classes",
          @class     
        ]}
1 Like

I do feel that allowing components to take arbitrary tailwind classes is an anti-pattern. Components should abstract away Tailwind, imo. If you want to allow customization, it should be done by providing an options API, like <.some_component color={:green} size={:large} />—That sort of thing, where :green and :large are predefined values. If you allow open season on component customization you start loose the a big part of the advantage of having them in the first place.

So if you were to abstract that pattern to be more generic what does that look like, or is the value in the pattern that its explicit by nature? I feel like what I see in our generators is the result to try and make components generic so that you can arbitrarily design at implementation of the pattern or rather closer to the surface is that makes sense.

It depends how you use them. I’m generally trying to build a design system when making components, so if one component is presented in many different ways across the site, that defeats the purpose a bit. Like if you allow Tailwind classes in the wild, you could end up with <.button class="mb-8 bg-green-500" /> in 6 places and <.button class="mb-4 bg-blue-300 /> in two others. It starts to loose meaning of what each one is for. It’s better to have <.submit /> and <.cancel />, for example and control layout with wrapping components.

Of course when I say stuff like “it’s better” I just mean it’s the way I like to do it. I’m by no means an expert on this stuff but used to working with constraint-based design systems (which Tailwind itself is, though they give you a lot of options out of the box).

1 Like

In all fairness I did ask, and the solution you provided fits the ask. So thanks.

I guess this is an example of where its maybe best to just ignore the generators because they are trying to be generic but that in doing so that is the problem. I wanted to keep what I already had vs doing what I need. One of the many pit falls of relying on generators I guess.

Ah right, if you use the generators you’re not going to get <.submit /> component (without rewriting the generator, which is not the best). Ya, definitely though if you use generators. I never do but suffer for it as making CRUD pages is super boring, lol. I do generally have a super basic one that I end up just copy-pasting and changing names in one swoop. It works all right.