Skip to main content
iVentureTeam

image_radio

A selection field rendered as clickable pictures. The images are matched to values by position, and the radio group has a hardcoded name, which is where both of its failure modes come from.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 27, 2026Updated August 27, 20266 min read
Technical nameimage_radio
Field typesselection
Viewsform
Modulewebsite, so any database with Website installed
Used in core1 occurrence, the course layout choice in website_slides
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0, Odoo 17.0, Odoo 16.0
No-code setupNo. Studio cannot pass the images option, and the images must be served from a module or attachment
Alternativesradio, selection, selection_badge, hr_homeworking_radio_image

What the image radio field does

Some choices are easier to make by looking than by reading. Picking a page layout, a course format or a template style is one of them: two words rarely convey what two thumbnails do instantly. image_radio exists for exactly that, and Odoo uses it in one place, the dialog where you choose whether a new course looks like a training or a documentation site.

It takes an ordinary selection field and renders each value as a picture. Clicking a picture writes that value. The selected one gets a highlight class, and the real radio inputs are visually hidden behind the images, so the control stays keyboard and screen-reader reachable while looking like a gallery.

The images are not stored on the record or derived from it. They come from an option on the field: a list of URLs, given in the same order as the field's selection values. That positional matching is simple and is also the source of most of the surprises described below.

What this means for your team

Visual pickers earn their place at the exact moment a user is deciding something they cannot undo cheaply. A course layout, a website theme, a document template: getting it right first time avoids a rebuild. Two thumbnails do that job in a second, and the pattern generalizes to any onboarding step where a screenshot explains more than a sentence.

The cost is maintenance. The pictures live outside the data model, so a change to the selection values, or even reordering them, silently changes which image sits above which label. That is not a technical failure that raises an error; it is a wrong picture, which nobody notices until a customer picks the wrong option. Any project using this widget should treat the selection order as part of the design.

Supported options in Odoo 19

One option, declared in the widget's descriptor. It is worth reading the exact behavior, because the option is required and its matching rule is positional. Read from the website module's field components file, Odoo 19.0.

OptionTypeWhat it does
imagesarray of stringsURLs of the images to show, in the same order as the field's selection values after empty entries are dropped. Declared as a required prop: omitting it raises a props error. Fewer images than values leaves the remaining tiles with an empty source.

The option is a required prop. It is declared as an array of strings with no optional marker, so leaving it out is a props error, not a silent fallback to labels. Fewer images than values is legal and produces an empty image source for the remainder, which renders as a broken or blank tile rather than as text.

Working examples

The core usage

<field name="channel_type" widget="image_radio"
       string="Choose a layout"
       options="{'images': [
           '/website_slides/static/src/img/channel-training-layout.png',
           '/website_slides/static/src/img/channel-documentation-layout.png']}"/>

Two selection values, two image paths, matched by position. This is the only place core uses the widget.

Serving your own images

<field name="template_style" widget="image_radio"
       options="{'images': ['/my_module/static/img/a.png',
                              '/my_module/static/img/b.png',
                              '/my_module/static/img/c.png']}"/>

Any URL the browser can fetch works, including an attachment route. Keep the array in the same order as the field's selection.

What breaks the mapping

# a padding entry is dropped before images are matched
style = fields.Selection([
    ('', ''),        # filtered out
    ('a', 'Alpha'),
    ('b', 'Beta'),
])

Values whose key and label are both empty are removed before the positional match, so an array written for the raw selection would be off by one.

One hardcoded group name, two colliding fields

The component does its work once, in setup. It reads the field's selection from the record's field definition, drops any entry whose key and label are both falsy, then maps what remains to triples of key, label and image, taking the image from the option array at the same index. Nothing re-runs that mapping later, so it reflects the selection as loaded.

Two details in the template are worth knowing before you use this widget twice on one screen. The radio inputs all carry the same hardcoded group name, and each input's id is the selection value itself. Put two image_radio fields on the same form and the browser treats every input as one radio group, so selecting a value in the second field visually clears the first, and the duplicate ids mean a label can point at the wrong input. Each field still writes its own value correctly, but the highlight is driven by the record rather than by the input state, so the visible result is confusing rather than wrong.

The other omission is read-only. The template never checks the read-only prop: the inputs render and the change handler writes, regardless of the record's state. On a form that is meant to be locked, hide the field rather than relying on read-only.

Finally, the highlight is a class comparison against the record's current value, not the input's checked state. That means the widget renders correctly after a server-side change, which is the upside of the same design that causes the group collision.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. Identical behavior on the development branch.
Odoo 19.0VerifiedVerified against the shipped source. Same as Odoo 18.
Odoo 18.0VerifiedIdentical behavior and the same declared option.
Odoo 17.0VerifiedThis is where the descriptor and the declared images option were introduced.
Odoo 16.0Partial / changedWorks the same, but the component was registered directly, so the images option was undeclared and read from the raw attributes.

Upgrade note. The XML stays identical across all supported versions. Odoo 16 registered the component class directly rather than a descriptor, so the images option was read from the raw attributes and never declared, meaning it did not appear in developer tooling on that version. Odoo 17 introduced the descriptor with the declared option, and Odoo 17 through 19 behave identically.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. The development branch is unstable and can still change; we re-verify the page after the release.

No change. The component, the declared option, the positional matching, the hardcoded input group name and the missing read-only handling are all identical on the development branch, and core still uses the widget in the course creation dialog.

Common problems and fixes

SymptomCause and fix
A props error mentioning imagesThe option is missing. It is declared as a required array of strings. Always pass options="{'images': [...]}" when using this widget.
The wrong picture sits above the wrong choiceThe array is matched to the selection by position, and either the selection order changed or an empty entry was dropped first. Re-order the array to match the selection, ignoring entries whose key and label are both empty.
Some tiles are blankThe images array is shorter than the selection, so the remaining values get an empty source. Provide exactly as many URLs as there are rendered values.
Two image pickers on one form interfereThe radio inputs share a hardcoded group name and their ids are the selection values. Use only one on a screen, or patch the template to give each instance its own group name.
The images stay clickable on a read-only recordThe template does not check the read-only prop. Hide the field with an invisible expression when the record must not be edited.
Missing widget errorThe website module is not installed in that database. Install Website, or use a plain radio or selection widget instead.

Image radio field vs the alternatives

WidgetBest forKey difference
image_radioChoosing between two or three options that are best understood visuallyRenders selection values as images matched by position, with the inputs visually hidden
radioPlain labelled choicesText labels, a working horizontal option and no artwork to maintain
selectionMore than a handful of valuesDropdown rendering, which scales where a gallery does not
selection_badgeCompact clickable choicesBadge styling rather than images
hr_homeworking_radio_imageIcon-based choices in the HR appFont icons hardcoded per value instead of configurable image URLs

Use this when the picture is the message. If the choices are best expressed as words with a small icon, an icon-based selection widget is lighter and does not need artwork maintained alongside the model. And if the choice is one of many rather than one of two or three, a dropdown beats any gallery, because a row of thumbnails stops scaling at about four.

Frequently asked questions

How does image_radio know which image belongs to which value?+
By position. The images array is matched index by index against the field's selection, after entries whose key and label are both empty are removed. There is no key-to-image mapping.
Is the images option required?+
Yes. It is declared as an array of strings with no optional marker, so leaving it out is a props error rather than a fallback to plain labels.
Can I use two image pickers on the same form?+
Not comfortably. Every input uses the same hardcoded radio group name and takes the selection value as its id, so the two fields collide visually. Each still saves its own value, but the highlighting confuses users.
Does it respect readonly?+
No. The template never checks the read-only prop, so the images stay clickable. Hide the field instead when the record should be locked.
Where do the images have to live?+
Anywhere the browser can fetch them. Core points at static files inside a module; an attachment URL works just as well.

Onboarding choices your customers get wrong?

The first screen of a configuration flow decides how much support the rest of it needs. We design and build those choices in Odoo, visual where it helps and plain where it does not, on versions 16 through 19.

Book a free consultation

How this page was produced

The declared option, the positional matching, the filtered selection entries, the hardcoded input group name and the absence of read-only handling were read from the website module's field component and template files on the Odoo 19.0 branch, with the usage taken from website_slides/views/slide_channel_add.xml. Version differences come from the same file on 16.0, 17.0 and 18.0, and the Odoo 20 statement from the public development branch. Spotted an error? Tell us and we will correct the page.