Fields can be configured to be readonly. In edit view, readonly fields prevent users from interacting with the field or changing its value, while still displaying the current value.

In index view, if readonly and index editable are both set to true, forms will be rendered with the readonly HTML attribute.

Supported fields

On index view, readonly is supported for all fields with the index editable option (see Index Edit).

On edit view, readonly is a global field option defined on Backpex.Field, so every built-in field type inherits it. It accepts either a boolean or a function (assigns -> boolean).

Built-in fields render readonly using one of three strategies:

Native readonly (text-like inputs)

These fields render the browser's native readonly attribute on their input, so the value is still focusable and selectable but cannot be changed:

disabled (control-style inputs)

Native readonly does not apply to these control types, so they render as disabled instead:

Custom readonly rendering

A few fields need tailored behavior beyond a single attribute:

  • Backpex.Fields.MultiSelect — dropdown is rendered as a labelled, non-interactive group; selected badges lose the remove control
  • Backpex.Fields.HasMany — dropdown is rendered as a labelled, non-interactive group; selected badges lose the remove control
  • Backpex.Fields.Upload — the drop target and "Upload a file" link are disabled, the cancel/remove buttons on pending and existing entries are hidden, and the existing-file list is still displayed so users can see what is attached. The server refuses uploads for a field that is readonly when the form opens, as it gets no upload config. On save, new and removed files of a readonly field are ignored, also when the field turned readonly while the form was open.
  • Backpex.Fields.InlineCRUD — nested row fields become readonly, and the per-row delete button and the add-row control are hidden entirely.
  • Backpex.Fields.HasManyThrough — the Actions column (edit/remove buttons) is hidden, the "new relational" button is disabled, and pivot and select inputs inside the modal are rendered as disabled.

Appearance and accessibility

Readonly inputs use the active theme's base-200 background, base-300 border, and base-content text color. Native selects and custom dropdowns use the same treatment, so non-editable values remain readable in light and dark themes. Validation errors retain their error colors.

Text-like inputs keep native readonly behavior: they remain keyboard-focusable, retain a visible focus outline, and allow text selection and copying. Controls that cannot use native readonly remain disabled or render as non-interactive groups. The visual treatment does not change which values are submitted with the form.

Configuration

To enable readonly for a field, you need to set the readonly option in the field configuration. This key must contain either a boolean value or a function that returns a boolean value.

# in your resource configuration file
def fields do
[
  rating: %{
    module: Backpex.Fields.Text,
    label: "Rating",
    readonly: fn assigns ->
        assigns.current_user.role in [:employee]
    end
  }
]
end
# in your resource configuration file
def fields do
[
  rating: %{
    module: Backpex.Fields.Text,
    label: "Rating",
    readonly: true
  }
]
end

Readonly for custom fields

You can also add readonly functionality to a custom field. To do this, you need to handle the readonly state in the Backpex.Field.render_form/1 function of your custom field. You can access the readonly value from the assigns, which will be true or false.

@impl Backpex.Field
def render_form(assigns) do
~H"""
<div>
  <Layout.field_container>
    <:label>
        <Layout.input_label for={@form[@name]} text={@field[:label]} />
    </:label>
    <BackpexForm.input
      type="text"
      field={@form[@name]}
      translate_error_fun={Backpex.Field.translate_error_fun(@field_options, assigns)}
      phx-debounce={Backpex.Field.debounce(@field_options, assigns)}
      phx-throttle={Backpex.Field.throttle(@field_options, assigns)}
      readonly={@readonly}
    />
  </Layout.field_container>
</div>
"""
end

If your readonly logic is more complex, you can also use a dedicated function that returns the markup for the readonly state.

@impl Backpex.Field
def render_form(%{readonly: true} = assigns) do
# Return readonly markup
end

def render_form(%{readonly: false} = assigns) do
# Return editable markup
end

When defining a custom field with index editable support, you need to handle the readonly state in Backpex.Field.render_index_form/1. There is also a readonly value in the assigns, which will be true or false.