# VelvetKit

## Form field (`UI::FormField`)

A label, hint and error message wired to any form control.

Add it: `bin/rails generate velvetkit:add form_field`

Also adds: `label`

### Arguments

```ruby
UI::FormField.new(label: nil, hint: nil, error: nil, required: false, disabled: false, label_size: :default, label_weight: :medium, form: nil, field: nil, for: nil, class: nil, **html_attrs)
```

### Notes

A label, a control, a hint and an error message, wired together: the label
points at the control, and the control is described by the hint and error.

Input, Textarea, NativeSelect and Select render through it, so they take
`hint:` and `error:` directly. Wrap any other control yourself and spread
`field.control_attributes` onto it:

  <%= render(UI::FormField.new(label: "Colour", hint: "Used for your avatar")) do |field| %>
    <%= tag.input(type: "color", name: "colour", **field.control_attributes) %>
  <% end %>

`error:` takes a message, or `true` to mark the control invalid without one.
Bound to a form (`form:` + `field:`), the model's errors on that attribute
are the message (for `team_id`, the `team` association's too, where
belongs_to puts "must exist"), and the attribute's human name is the label
(`label:` overrides it, `label: false` opts out). Nothing to show means no
wrapper: just the control.

### Example: On the built-in controls

```erb
<div class="flex flex-col gap-6 max-w-md">
  <%= render(UI::Input.new(type: :email, label: "Email", hint: "We'll only use it to send receipts.", placeholder: "you@example.com")) %>
  <%= render(UI::Input.new(label: "Username", value: "a", error: "Use at least 3 characters.")) %>
  <%= render(UI::Textarea.new(label: "Bio", hint: "Shown on your public profile.", rows: 3)) %>
  <%= render(UI::NativeSelect.new(label: "Team", options: %w[Design Engineering], prompt: "Choose a team", error: "Choose a team to continue.")) %>
  <%= render(UI::Select.new(label: "Plan", options: %w[Free Pro Team], hint: "You can change plans at any time.")) %>
</div>
```

### Example: Errors from the model

```erb
<%# In an app, @signup comes back from a failed save. Bound fields show its errors on their own. %>
<% signup = Struct.new(:email, :bio) do
     include ActiveModel::Validations
     def self.model_name = ActiveModel::Name.new(self, nil, "Signup")
     validates :email, presence: true
     validates :bio, length: { minimum: 20 }
   end.new("", "Hi").tap(&:validate) %>
<%= form_with(model: signup, url: "#", class: "flex flex-col gap-6 max-w-md") do |f| %>
  <%= render(UI::Input.new(form: f, field: :email, type: :email)) %>
  <%= render(UI::Textarea.new(form: f, field: :bio, hint: "A sentence or two about you.")) %>
<% end %>
```

### Example: Your own control

```erb
<div class="max-w-md">
  <%= render(UI::FormField.new(label: "Accent colour", hint: "Used for your avatar and links.")) do |field| %>
    <%= tag.input(type: "color", name: "accent", value: "#4d1a7f",
                  class: "h-9 w-16 rounded-md border border-input bg-background p-1", **field.control_attributes) %>
  <% end %>
</div>
```
