Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Making Html Input Controls Truly ReadOnly


:P
On this page:

Locked Envelope For Your Eyes Only

Although HTML has support for readonly control content, the behavior is not really well implemented in Web browsers. readonly display some minimal alternate styling (which you can customize via CSS), but even then the behavior you get is still not optimal because the control is still selectable and stills shows a focus rectangle:

If you just use something like this:

<input type="text" readonly class="image-quantity" />

You get a result like this in a text box (here in a list of images):

ReadOnly Text Box still shows focus
Figure 1 - The default readonly behavior on input controls leaves controls still selectable and show a focus rectangle, even though the text can't be edited.

You can see that the textbox is still selectable with the mouse and you can still tab into it. It still behaves like an edit control, except you can't change the value which is pretty annoying to users.

Some frameworks like Bootstrap do default styling for readonly so you see read only fields that display differently with greyed out or low-lighted content. Here's Bootstrap's default version of that:

Read Only But Active
Figure 2 - Default readonly styling often only addresses the display, not the UI activation behavior even though you can't edit the input.

That's good enough for a visual indicator, but unfortunately - as before - the field is still UI enabled, so you can click on it or tab into it to get a focus rectangle. To the user it still looks like the field is editable, even though it is not!

readonly vs. disabled - Or both?

Html has both readonly and disabled which from a UI perspective often have overlapping functionality and behaviors. disable actually disables field input and so that would solve our focusability issue.

The simplest solution to this problem is to either use disabled or combine disabled and readonly on the control like this:

<input type="text" readonly disabled class="image-quantity" />

<!--  or simpler and most likely all that you need -->
<input type="text" disabled class="image-quantity" />

Now the input field becomes non-accessible altogether without any focus.

So, could we just use disabled instead of readonly? That depends:

"The difference between disabled and readonly is that read-only controls can still function and are still focusable, whereas disabled controls can not receive focus and are not submitted with the form and generally do not function as controls until they are enabled."
     MDN Documentation

IOW:

  • A readonly input is submitted but cannot be modified by the user.
  • A readonly input control is still selectable but cannot be modified by the user.
  • A disabled input is not submitted and cannot be modified by the user.
  • A disabled input control is not selectable and cannot be modified by the user.

Bottom line: disabled is only a problem if you're working with server side Html form submissions and you need to submit the disabled field.

For client side applications that's generally a non-issue, but it is something to keep in mind!

I would argue that in most cases where you think you need readonly, you probably want to use disabled instead. But if you're like me, that probably wouldn't be your first thought. The typical use case for readonly is when you have a mixed mode form where you toggle between edit and display modes and readonly seems like the natural choice for displaying... well, read only content 😄. Disabled doesn't seem natural here, but as per this discussion, most likely it's the better choice.

Fixing Focus: CSS Styling and Html Attributes

Assuming you want to need to stick with readonly by itself, you can also wring out non-selectable behavior with only readonly behavior, but it takes a little extra effort.

By default HTML base style sheets or frameworks like Bootstrap or Material provide low lighting formatting for ReadOnly controls. But if you do custom control styling like I'm doing in Figure 1 that default read-only styling mostly goes out of the window. In Figure 1 the same styling applies regardless of readonly status of the control.

Thankfully you can override the styling relatively easily with CSS like this to provide better read only behavior:

 .image-quantity[readonly]
 {
     border: none;            
     opacity: 0.7;
     pointer-events: none;
 }

There are plenty of visual tricks you can use to show disabled content, from grey backgrounds, to opacity or removing content altogether. I like opacity as in indicator both for read only and disabled content.

The pointer-events attribute is one that's easy to overlook - it's what prevents you from UI activating with a mouse so a click on the field will not activate it.

This CSS gets us most of the way:

  • Displays low-lighted to show the read only behavior
  • Doesn't allow for clicking into the control to set focus

That's a big improvement!

Fixing Tab Focus

Unfortunately there's one more thing that affects the control's focus: Using Tab selection in the page which lets you use keyboard navigation of editable controls. readonly input controls are still included in the default tab order and while you generally don't tab into fields, if you're in a nearby field and tab or arrow through you may still end up on that field with focus at some point.

There's a workaround for this issue as well: You can use the tabindex attribute on Html Input controls to control how tab focus and tab ordering is handled:

<!-- no tab focus -->
<input type="text" readonly tabindex="-1" class="image-quantity" />

<!-- Default tab ordering  -->
<input type="text" readonly tabindex="0" class="image-quantity" />

<!-- Specific Tab ordering  -->
<input type="text" readonly tabindex="5" class="image-quantity" />

The default is 0 which uses automatic tab ordering which is in the order the controls appear on the page. Set at 0 readonly controls still receive tab focus, unfortunately.

You can also provide specific tab ordering where higher numbers are accessed later in the tab order. This really only works well if you apply it to all controls, which isn't always easy to do especially in component based applications where a page is made up of many components and the tab order isn't clear cut unless you very deliberately use a numbering scheme appropriate for each component/page.

Finally the most important value for this post: -1 removes the control completely from the tab ordering sequence so you can't tab into the field.

Put it Together in Html

So now we have what we want via:

  • Low Lighted CSS styling
  • Disabled pointer events with pointer-events: none
  • No tab order access via tabindex="-1"
 .image-quantity[readonly]
 {
     border: none;            
     opacity: 0.7;
     pointer-events: none;
 }
 
 <input type="text" readonly 
        tabindex="-1" 
        class="image-quantity" />

If you want to be more generic about it you can also apply the CSS globally so it applies to all input controls:

input[readonly], textarea[readonly]
{
     opacity: 0.7;
     pointer-events: none;
}

Note that readonly only applies on text input controls like input and textarea, not on other controls like select or button. For those controls disabled is likely the better Html attribute to use.

Put it together with Conditional Rendering in VueJs

To put this into the practical context of the example I show in Figure 1, let's look at an application implementation that uses dynamic assignment as part of a VueJs app.

I'm using VueJs and the readonly display state is determined by an isReadOnly model property that is triggered as part of the application depending on operating mode. Basically while in production mode you can change the quantities, once the order is submitted it's read only.

The relevant logic binding both the readonly style and the tabindex Html attribute is:

<style>
 .image-quantity {
     width: 2.3em;
     font-size: 1.3em;
     padding: 0 0.4em
 }
 .image-quantity[readonly]
 {
     border: none;            
     opacity: 0.7;
     pointer-events: none;
 }
</style>
...
<input v-model="image.quantity"
            v-on:change="updateQuantity(image)"
            v-bind:readonly="isReadOnly"
            v-bind:tabindex="isReadOnly ? -1 : auto"
            class="number image-quantity"  />
</div>

The rendered plain Html looks like this:

<input readonly="readonly" tabindex="-1" class="number image-quantity">

And that now works:

Read Only With Styling And Tags

with an unselectable input box. You can still select the text via text selection, but there's no focus rectangle or other highlighting other than the text selection highlight.

Summary

If you want to get input controls that can be accessed by users you have a couple of options:

  • Use disabled if you can and don't need to submit to the server
  • Use readonly and apply CSS styling with point-events: none and Html tabindex="-1"

It sure would be nice if default CSS style sheets and even those in some CSS frameworks handled readonly fields better out of the box. At minimum a framework like Bootstrap should probably have a global rule that includes the pointer-events: none:

input[readonly], textarea[readonly]
{
    ...
    pointer-events: none;
}

to at least disallow clicking into the field. That still leaves tabbing but that can't be fixed with CSS. Heck it would be nice if tab behavior could also be controlled via CSS. But alas it's W3C 😄.

As with so many things in Html and CSS there are plenty of solutions to overlooked behaviors like this, it's finding in non-obvious and often disconnected places, that's the hard part.

And it's also the reason for posts about basic topics like this: It lets me find this information in a well-known place in the future. Maybe some of you find it useful too.

Resources

Posted in Html  CSS  


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2025