Building A Beautiful Text Input in React

  • Have a focussed and un-focussed state with a smart transition.
  • Accept a pre-defined value, placeholder and label.
  • Accept errors passed in from an external function.
  • Be ‘lockable’.
The finished article.
import React, { Component } from 'react';export default class TextInput extends Component {
render() {
return (
<div className="field" />
)
}
}
.field {
width: 100%;
height: 56px;
border-radius: 4px;
position: relative;
background-color: rgba(255,255,255,0.3);
transition: 0.3s all;
}
.field:hover {
background-color: rgba(255, 255, 255, 0.45);
box-shadow: 0px 4px 20px 0px rgba(0, 0, 0, 0.05);
}
import React from 'react';
import PropTypes from 'prop-types';
import './text-input-style.css';
export default class Field extends Component {
static propTypes = {
id: PropTypes.string.isRequired,
locked: PropTypes.bool,
focussed: PropTypes.bool,
value: PropTypes.string,
error: PropTypes.string,
label: PropTypes.string,
onChange: PropTypes.func,
};
static defaultProps = {
locked: false,
focussed: false,
value: '',
error: '',
label: '',
onChange: () => '',
};
render() {
[...]
[...]
predicted: '',
onChange: () => '',
};
constructor(props) {
super(props);
this.state = {
focussed: (props.locked && props.focussed) || false,
value: props.value || '',
error: props.error || '',
label: props.label || '',
};
}
render() {
[...]
[...]
render() {
const { focussed, value, error, label } = this.state;
const { id, type, locked } = this.props;
return (
<div className="field">
<input
id={id}
type="text"
value={value}
placeholder={label}
/>
</div>
);
[...]
[...]
onChange = event => {
const { id } = this.props;
const value = event.target.value;
this.setState({ value, error: '' });
return this.props.onChange(id, value);
}
[...]
[...]
<input
id={id}
type="text"
value={value}
placeholder={label}
onChange={this.onChange}
/>
[...]
[...]
.field.focussed input {
padding: 24px 16px 8px 16px;
}
.field.focussed input + label {
top: 4px;
opacity: 1;
color: #28cc70;
}
.field.locked {
pointer-events: none;
}
.field input {
width: 100%;
height: 56px;
position: relative;
padding: 0px 16px;
border: none;
border-radius: 4px;
font-family: 'Gotham SSm A', 'Gotham SSm B', sans-serif;
font-size: 16px;
font-weight: 400;
line-height: normal;
background-color: transparent;
color: #282828;
outline: none;
box-shadow: 0px 4px 20px 0px transparent;
transition: 0.3s background-color ease-in-out, 0.3s box-shadow ease-in-out, 0.1s padding ease-in-out;
-webkit-appearance: none;
}
.field input::-webkit-input-placeholder {
color: rgba(255, 255, 255, 0.8);
}
.field input::-moz-placeholder {
color: rgba(255, 255, 255, 0.8);
}
.field input:-ms-input-placeholder {
color: rgba(255, 255, 255, 0.8);
}
.field input:-moz-placeholder {
color: rgba(255, 255, 255, 0.8);
}
[...]
const fieldClassName = `field ${(locked ? focussed : focussed || value) && 'focussed'} ${locked && !focussed && 'locked'}`;
return (
<div className={fieldClassName}>
[...]
[...]
<input
id={id}
type="text"
value={value}
placeholder={label}
onChange={this.onChange}
onFocus={() => !locked && this.setState({ focussed: true })}
onBlur={() => !locked && this.setState({ focussed: false })}
/>
[...]
[...]
onBlur={() => !locked && this.setState({ focussed: false })}
/>
<label htmlFor={id} className={error && 'error'}>
{error || label}
</label>
</div>
[...]
.field input + label {
position: absolute;
top: 24px;
left: 16px;
font-family: 'Gotham SSm A', 'Gotham SSm B', sans-serif;
font-size: 12px;
font-weight: 600;
line-height: 24px;
color: #ffffff;
opacity: 0;
pointer-events: none;
transition: 0.1s all ease-in-out;
}
.field input + label.error {
color: #ec392f;
}

The End Result

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store