hvanderheide

hvanderheide

Tailwind Form Field components

I’m trying to get some form styling going with tailwind in a (live) view. The approach below works, however the heex template seems to get really cluttered with repetitive code, especially as I introduce error stylings. As tailwind seems generally common within the phoenix world, I thought there might be a tailwind form field renderer already, but I was unable to find one.

 <div class="col-span-6">
  <%= label f, :email, class: ["block text-sm font-medium text-gray-700"] %>
  <div class="mt-1 sm:mt-0 sm:col-span-2 relative rounded-md shadow-sm">
    <%= email_input f, :email, required: true, autocomplete: false, value: @card.email, class: ["mt-1 block w-full shadow-sm sm:text-sm rounded-md"] ++ [(if f.errors[:email], do: "pr-10 border-red-300 text-red-900 placeholder-red-300 focus:outline-none focus:ring-red-500 focus:border-red-500", else: "focus:ring-indigo-500 focus:border-indigo-500 shadow-sm border-gray-300")] %>
    <%= if f.errors[:email] do %>
      <div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
        <svg class="h-5 w-5 text-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
          <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
        </svg>
      </div>
    <% end %>
  </div>
  <%= if message = f.errors[:email] do %>
    <p class="mt-2 text-sm text-red-600" id="email-error"><%= translate_error(message) %></p>
  <% end %>
</div>

So I was thinking, coming from python webframeworks, I should probably be able to create a FormFieldComponent and hand it the form, field(name, type) and instance to render properly. Without taking fieldsets into account that would probably lead to a template with a bunch of live_component tags.

Is this a good approach, or is there a more common way to do this?

Edit: It seems petal.build kind of solved this problem, although I’m not sure they fully adopted tailwind’s styling (e.g. svg elements seem missing from formfield errors)

Marked As Solved

chrismccord

chrismccord

Creator of Phoenix

You want to compartmentalize your forms and styling into their own function components. For example, you templates should look something like this:

    <.form let={f} for={@changeset}>
      <.input f={f} field={:email} type="email" value={@card.email} />
      <.input f={f} field={:title} type="text />
    </.form>

and you can define input functions which wraps all your styling and error tags:

  def input(%{type: "email"} = assigns) do
    ~H"""
    <%= label @f, @field, class: ["block text-sm font-medium text-gray-700"] %>
    <div class="mt-1 sm:mt-0 sm:col-span-2 relative rounded-md shadow-sm">
      <%= email_input @f, @field, required: true, autocomplete: false, value: @value, class: ["mt-1 block w-full shadow-sm sm:text-sm rounded-md"] ++ [(if f.errors[:email], do: "pr-10 border-red-300 text-red-900 placeholder-red-300 focus:outline-none focus:ring-red-500 focus:border-red-500", else: "focus:ring-indigo-500 focus:border-indigo-500 shadow-sm border-gray-300")] %>
      <%= if @f.errors[@field] do %>
        <div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
          <svg class="h-5 w-5 text-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
            <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
          </svg>
        </div>
      <% end %>
    </div>
    """
  end

  def input(%{type: "text"} = assigns) do
    ...
  end
13
Post #3

Also Liked

chrismccord

chrismccord

Creator of Phoenix

To each their own. With tailwind I never touch css files :slight_smile:
No problem going that direction if you want to, but I found function components are enough to avoid duplication that I care about.

chrismccord

chrismccord

Creator of Phoenix

yeah to be clear, I’m saying I can de-dup within elixir rather than using css rules, for example using your label_class example and the upcoming 0.18 declarative assigns:

  attr :type, :string, required: true
  attr :label_class, :string, default: "block text-sm font-medium text-gray-700"

Or, you could support an optional <:label> slot which the caller can use to pass whatever they wanted, so you have caller customization options depending on what you want.

chrismccord

chrismccord

Creator of Phoenix

Yes your function component would make use of this internally but the above comment still applies

Last Post!

cvkmohan

cvkmohan

Got it. Thanks.

Where Next?

Popular in Questions Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

Other popular topics Top

joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54996 245
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 49134 226
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New

We're in Beta

About us Mission Statement