Floating label inputs are input fields whose placeholder text animates into a label when you start typing. You might recognize this pattern from Google’s Material Design.
In this post I’ll explain how you can build your own floating label input using React and a little CSS.
If you just want the code, scroll to the end. You’ll find a Codesanbox where you can see the full code, try out the result, and customize it to your liking.
Step 1: Put the label and input inside a container div
Normally a label sits on top of an input field. But for our use case, we want the label to be in two positions depending on the interaction state:
Empty state: Sitting in the middle of the input field, acting as placeholder text
Filled/typing state: Pinned to the top left corner of the input field, acting as a label.
The HTML looks something like this:
<div className="input-container"> <input type="text" /> <label>Label</label> </div>
Step 2: Set the container div position to relative so we can have more control over the position of the label
In your CSS file, set the position of the container div to “relative”. Also, set display to “flex” and flex-direction to “column”.
.input-container { position: relative; display: flex; flex-direction: column; }
Step 3: Position the label in it’s “placeholder” state
Now that the we’ve set the parent div to use relative positioning, we can use absolute positioning on our label to put it in the middle of the field so it looks like placeholder text. We can also add a few other styles to make it look good.
.input-container label { position: absolute; pointer-events: none; transform: translate(0, 23px) scale(1); transform-origin: top left; transition: 200ms cubic-bezier(0, 0, 0.2, 1) 0ms; color: #6f81a5; font-size: 16px; line-height: 1; left: 16px; }
Note the line: transform-origin: top left. This is important. Without this, if we were to scale down the label into it’s “label state”, the transform origin would be in the middle, which would make it impossible for us to pin a label of any length exactly to the top left corner. By setting the transform-origin to top left, we can get it right every time.
Step 3: Use :focus-within to set the position of the label when the input is focused
CSS has a handy pseudo-selector called ":focus-within”. This lets us target a parent div whose child is currently in focus. We need this so we can say the following: “Hey parent div, when there is focus within you (the input field is in focus), then style the label like this”:
.input-container:focus-within label { transform: translate(0, 12px) scale(0.8); color: #0a53e4; }
This is the “label” state of the label element, where we scale it down and pin it to the top left corner of the field.
Step 4: Use React state to handle the case where the field is filled but not in focus
We’re almost there, but we’re running into the limitations of CSS. We need a way to change the styling based on whether or not the input has text inside of it.
If there is text, put label into label state, even if the field is not in focus
If there is no text, put label into placeholder text
To do this, we just need to do the following in React:
Use useState to store the value of the input field
onChange, update the value
Conditionally add a class of “filled” to the label as long as value is not an empty string
Our React component now looks like this:
function TextInput({ type = 'text', label }) { const [value, setValue] = useState(''); function handleChange(e) { setValue(e.target.value); } return ( <div className="input-container"> <input type={type} value={value} onChange={handleChange} /> <label className={value && 'filled'} htmlFor={name}> {label} </label> </div> ); }
Try out the demo in Codesandbox
Feel free to fork the code as customize it to your liking.