Accessibility first: text input
Tags: a11y, materialdesign, css, webdev
Published: 2018-11-30 15:05:35

---
title: Accessibility first: text input
published: true
description: Accessible material design text input
tags: a11y, materialdesign, css, webdev
series: Accessibility first
cover_image: https://thepracticaldev.s3.amazonaws.com/i/utzjmku25jw01skz2hsp.png
---

Before I start I should let you know I'll be using [:placeholder-shown](https://developer.mozilla.org/en-US/docs/Web/CSS/:placeholder-shown).

 > This is an **[experimental technology](https://developer.mozilla.org/en-US/docs/MDN/Contribute/Guidelines/Conventions_definitions#Experimental)**
Check the [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/:placeholder-shown#Browser_compatibility) table carefully before using this in production.

Though it's been implemented in both Firefox and Chrome, Edge has yet to start developing the technology. That being said there is no other way to achieve this without using Javascript.

## What do we want to create?

Today we want to make a material design input box that can handle all the input states and does not use any Javascript.

{% jsfiddle https://jsfiddle.net/link2twenty/ax96j04y/ result %}

## The markup

If you've read the [first entry](https://dev.to/link2twenty/accessibility-first-toggle-switches-3obj) in this series you'll probably start to recognise my 'style'. I like to have a label element that wraps my input and then have some spans I can animate later.

```html
<label class="md_textbox">
  <input type="text" placeholder="Input field"/>
  <span class="md_textbox__label">Input field</span>
  <span class="md_textbox__line"></span>
</label>
```
You'll notice there is some repeatition here, technically, we could have `placeholder=" "` rather than the whole string but it's good to plan for your CSS to fail.

{% jsfiddle https://jsfiddle.net/link2twenty/wnLmxqb9/ html result %}

## Styles

Just like with the toggle in my previous post there are several states we will need to support with our styles, they are:

 - Empty
 - Focused
 - Containing valid text
 - Containing invalid text
 - Disabled
 - Required
 - Read-only

### Empty (much of the legwork is done here)

First I'll import Open Sans for a material look and set my usual display and margin settings.

```css
@import url(http://fonts.googleapis.com/css?family=Open+Sans);
.md_textbox {
  display: inline-flex;
  position: relative;
  font-family: "Open Sans";
  align-items: center;
  margin: 1.2em 0.6em 0.9em 0.6em;
}
```
Now we only want our `.md_textbox__label` to show and not the placeholder text so lets make that invisible.

```css
.md_textbox [type=text]::placeholder {
  color: rgba(0, 0, 0, 0)
}
```
Next, we'll want our label to be in the placeholder's position so we'll give it an absolute position and move it over to left. While we're at it I know I want the line to be on the far left too and the space for the error messages so let's bundle it all together.

I know they'll all need to animate too so I've given them a transition, this is from the material design spec.

```css
.md_textbox .md_textbox__label,
.md_textbox .md_textbox__line,
.md_textbox .md_textbox__line::before {
  position: absolute;
  transition: transform 150ms cubic-bezier(0.4, 0.0, 0.2, 1);
  left: 0;
}
```
OK, we still have a little housekeeping to do, when we mouse over our replacement placeholder we should still see the text cursor also now's the time to set the colour for that text. We also know we'll need to scale the label when we move it but we'll want to scale it from the left edge so let's add that to our code too.

```css
.md_textbox .md_textbox__label {
  transform-origin: 0 50%;
  cursor: text;
  color: #424242;
}
```

Let's now do the prep for the line we need to position it at the bottom and give it a colour. For the animation, I think we'll use a transform so let's initialise the element with `scale(0, 1)` so it's hidden.

```css
.md_textbox .md_textbox__line {
  bottom: 0;
  height: 2px;
  width: 100%;
  transform: scale(0, 1);
  background: #00897B;
}
```
This is the last big of legwork before we can start styling our actual textbox, we know we're going to have an error message slot so let's position that. We'll not include content so it won't actually render anything.

```css
.md_textbox .md_textbox__line::before {
  content: '';
  margin-top: 2px;
  font-size: 0.7em;
}
```

**Finally** we can style our text box. I'm going to make it a little wider, remove the default border, remove the outline, I wouldn't normally like doing this but we'll be showing focus another way, make the background transparent and add a line under our text.

```css
.md_textbox [type=text] {
  width: 200px;
  outline: none;
  border: none;
  border-bottom: 1px solid #BDBDBD;
  background: rgba(0, 0, 0, 0);
}
```

Phew that was a lot of css almost 50 lines and our input isn't even functional yet that being said we've got the bulk of the styles out of the way now.

![MD input box Standard](https://thepracticaldev.s3.amazonaws.com/i/x4pu6p6eq9zhpgxllyq9.png)


### Focused

When we focus on the text box we want two things to happen, one we want the label to get out the way so we can see where we're typing and two we want a bold line to appear under the element to let us know it's focused.

To move the label we'll use a transform, we should also make it a little smaller but make the text bold and the 'active' colour.

```css
.md_textbox [type=text]:focus~.md_textbox__label {
  transform: translateY(-1em) scale(0.8);
  font-weight: bold;
  color: #00897B;
}
```

To make the line appear we just need to set the scale, from earlier, back to 1 like so

```css
.md_textbox [type=text]:focus~.md_textbox__line {
  transform: scale(1);
}
```
And that's all we need to do for focusing, much simpler.

![MD input box Focusing](https://thepracticaldev.s3.amazonaws.com/i/khvi7o4xujwxq1xmcmu8.gif)

### Containing valid text

This bit is even easier but is the first part that uses `:placeholder-shown` so be aware support is not that great for this feature yet.

We simply need to get the label out of the way, no need to recolour it.

```css
.md_textbox [type=text]:not(:placeholder-shown)~.md_textbox__label {
  transform: translateY(-1em) scale(0.8);
  font-weight: bold;
}
```
![MD input box text](https://thepracticaldev.s3.amazonaws.com/i/ekvnjxcne1dlf1dn51i7.png)

Let me explain what `[type=text]:not(:placeholder-shown)` actually does.
`[type=text]` selects the input, `:not()` inverses the logic of what ever's within its parentheses and `:placeholder-shown` will only apply styles if a placeholder is there, a placeholder is hidden when text is input so `:placeholder-shown` becomes false.

`[type=text]:not(:placeholder-shown)` for the **textbox** if the placeholder is **not** shown apply these styles.

### Containing invalid text

For the invalid styles, we want to display a message that says "Invalid input" and we want to throw some red at it to imply an error.

First, we'll show the bottom line and make it red.

```css
.md_textbox [type=text]:invalid~.md_textbox__line {
  background: #B00020;
  transform: scale(1);
}
```

Let's make the label red too

```css
.md_textbox [type=text]:invalid~.md_textbox__label {
  color: #B00020;
}
```

Now let's add that error message if you remember we've already made space for it, so all we need to do is add content and perhaps colour it. I know we'll make it red.

```css
.md_textbox [type=text]:invalid~.md_textbox__line::before {
  content: 'Invalid input';
  color: #B00020;
}
```
![MD input box validation](https://thepracticaldev.s3.amazonaws.com/i/2qzhea3dg4vq7s1u4gxk.gif)

### Disabled

When we disable the textbox we need to show the user that they can't click on the element. Keyboard users are lucky in that disabled textboxes are just skipped over.

To show it's disabled I've decided to make the whole element slightly translucent and colour the actual textbox a light grey. I'll also change the cursor to `not-allowed`.

```css
.md_textbox [type=text]:disabled {
  cursor: not-allowed;
  border: none;
  background: #BDBDBD;
  opacity: 0.6;
}

.md_textbox [type=text]:disabled~.md_textbox__label {
  cursor: not-allowed;
  opacity: 0.6;
}
```
![MD input box disabled](https://thepracticaldev.s3.amazonaws.com/i/ab9g4eciti1rkbntnmxe.png)

### Required

When an input is empty but has the required attribute it returns that it is invalid because of this we need `:required` to be later in our CSS so we can override the styles set by `:invalid`.

We want the change the text content and colour, we'll change the colour to the standard grey we've been using and the text to "*Required".

```css
.md_textbox [type=text]:invalid:required~.md_textbox__line::before {
  content: '*Required';
  color: #424242;
}
```
The line and label will also be appearing as red so let's change them back to their usual colours too.

```css
.md_textbox [type=text]:required:placeholder-shown:not(:focus)~.md_textbox__line {
  background: #BDBDBD;
}

.md_textbox [type=text]:required:placeholder-shown:not(:focus)~.md_textbox__label {
  color: #424242;
}
```
When they're focused or filled in we don't mind the `:invalid`, `:focus` or `:not(:placeholder-shown)` styles taking over so I've used `:placeholder-shown:not(:focus)` to specify those states are not included.

You'll notice we get the added bonus of some on hover text, that's the joy of relying on the platform.

![MD input box required](https://thepracticaldev.s3.amazonaws.com/i/qn165j3eie3t9smban6b.gif)

### Read-only

The difference between read-only and disabled is that read-only is still in the tab index. We need to show the user they can't edit the text but they can still select it.

Honestly, I'm not sure I achieved this, so would love some feedback about how to improve it.

What I did was remove the bottom border from the textbox and made sure the line would never appear.

```css
.md_textbox [type=text]:read-only {
  border: none;
}

.md_textbox [type=text]:read-only~.md_textbox__line {
  transform: scale(0, 1);
}
```

This bit I wasn't so sure on when there is no text present and the user focuses on the text box I decided to hold the animation.

```css
.md_textbox [type=text]:read-only:placeholder-shown~.md_textbox__label {
  transform: translateY(0) scale(1);
}
```

## Finishing up

{% jsfiddle https://jsfiddle.net/link2twenty/ax96j04y/ result html css%}

In the future CSS spec we have [`:has()`](https://drafts.csswg.org/selectors-4/#relational), though it's not supported by any browsers yet. With that, we could have done something like

```css
.md_textbox:has(> [type=text]:not(:placeholder-shown))::after {
  bottom: 0;
  height: 2px;
  width: 100%;
  transform: scale(0, 1);
  background: #00897B;
}
```

meaning we could easily remove our `.md_textbox__line` span but that's for another post.