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
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
Also Liked
chrismccord
To each their own. With tailwind I never touch css files ![]()
No problem going that direction if you want to, but I found function components are enough to avoid duplication that I care about.
chrismccord
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
Yes your function component would make use of this internally but the above comment still applies
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #code-sync
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








