Today, we are going to build a cool form using CSS. I am specifically going to use followings.
1. solid color gradients
2. sibling selectors
3. ::input-placeholder pseudo-element
4. :focus, :invalid, placeholder-shown, :checked pseudo classes
Please note that I have learned this from the udemy course by Jonas Schmedtmann.
Let’s take a look at the final result first
Form
There is a couple of things to note here.
- Animation from placeholder to label to indicate the field
- CSS created radio button
- cool shape with background image
Let’s take a look at the code now
HTML Code
<section class="section-book">
<div class="row">
<div class="book">
<div class="book__form">
<form action="#" class="form">
<div class="u-margin-bottom-medium">
<h2 class="heading-secondary">
Start booking now
</h2>
</div>
<div class="form__group">
<input type="text" class="form__input" placeholder="Full name" id="name" required>
<label for="name" class="form__label">Full name</label>
</div>
<div class="form__group">
<input type="email" class="form__input" placeholder="Email address" id="email" required>
<label for="email" class="form__label">Email address</label>
</div>
<div class="form__group u-margin-bottom-medium">
<div class="form__radio-group">
<input type="radio" class="form__radio-input" id="small" name="size">
<label for="small" class="form__radio-label">
<span class="form__radio-button"></span>
Small tour group
</label>
</div>
<div class="form__radio-group">
<input type="radio" class="form__radio-input" id="large" name="size">
<label for="large" class="form__radio-label">
<span class="form__radio-button"></span>
Large tour group
</label>
</div>
</div>
<div class="form__group">
<button class="btn btn--green">Next step →</button>
</div>
</form>
</div>
</div>
</div>
</section>There are multiple form groups under form to click the form when label is clicked. It is a typical html forms you can find.
CSS Code
This is the CSS code. Please note that I used SCSS and there are some variables for colors and font size which could be replaced with normal values you would like. I am going to explain each class separately for better understanding
section-book and book
.section-book {
padding: 15rem 0;
background-image: linear-gradient(to right bottom, $color-primary-light, $color-primary-dark);
}
.book {
background-image: linear-gradient(105deg,
rgba($color-white, .9) 0%,
rgba($color-white, .9) 50%,
transparent 50%),
url(../img/nat-10.jpg);
background-size: cover;
// this is also possible
// background-size: 100%;
border-radius: 3px;
box-shadow: 0 1.5rem 4rem rgba($color-black, .2);
height: 50rem;
&__form {
width: 50%;
padding: 6rem;
}
}As you can see in the example, there is a trapezoid shape (form) on top of the background image. You could use clip-path to make the shape. But you could also use another properties of linear-gradient. In linear-gradient, you can specify color at each percentage. And as you see in the code, white color at 50% and transparent at 50% will actually make the shape because it’s gradient – white and transparent will be mixed at 50%.
Form classes
.form {
&__group:not(:last-child) {
margin-bottom: 2rem;
}
&__input {
font-size: 1.5rem;
font-family: inherit;
color: inherit;
padding: 1.5rem 2rem;
border-radius: 2px;
background-color: rgba($color-white, .5);
border: none;
border-bottom: 3px solid transparent;
width: 90%;
display: block;
transition: all .3s;
&:focus {
outline: none;
box-shadow: 0 1rem 2rem rgba($color-black, .1);
border-bottom: 3px solid $color-primary;
}
&:focus:invalid {
border-bottom: 3px solid $color-secondary-dark;
}
&::-webkit-input-placeholder {
color: $color-gray-dark-2;
}
}
&__label {
font-size: 1.2rem;
font-weight: 700;
margin-left: 2rem;
margin-top: .7rem;
display: block;
transition: all .3s;
}
// select when placeholder is shown in the input
// adjacent sibling selector (only if it's adjacent. no other elements in the between allowed
// + adjacent (order matters), ~ general
&__input:placeholder-shown + &__label{
opacity: 0;
visibility: hidden;
transform: translateY(-4rem);
}
&__radio-group {
width: 49%;
display: inline-block;
}
&__radio-input {
display: none;
}
&__radio-label {
font-size: $default_font-size;
cursor: pointer;
position: relative;
padding-left: 4.5rem;
}
&__radio-button {
height: 3rem;
width: 3rem;
border: 5px solid $color-primary;
border-radius: 50%;
display: inline-block;
// position just need to be specified for child position absolute
// it doesn't need to be relative
position: absolute;
left: 0;
top: -.4;
// content, display property is mandatory for all pseudo element
&::after {
content: "";
display: block;
height: 1.3rem;
width: 1.3rem;
border-radius: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: $color-primary;
opacity: 0;
transition: opacity .2s;
}
}
// as soon as selected, radio input checked will be on. then this selector will be enabled
// radio-label (sibling) and then child ::after
&__radio-input:checked ~ &__radio-label &__radio-button::after {
opacity: 1;
}
}This is the nutshell of the form and let’s take a look some important classes.
form__input
Mainly it has all common css properties such as font-size, border, padding and so on. Things I would like to notes are pseudo classes such as :focus, :invalid, ::-webkit-input-placeholder.
:focus is selected when input is selected (focus). Since I didn’t want any default behavior, it is overriden that it has bottom outline with green color and box shadow
:invalid is selected when the input is evaluated to be invalid. Take a look at email address example in the video. Since I used input email type in the html, it will automatically evaluate as I type the letters. &:focus:invalid means I only want to show orange bottom color to indicate the error only if the input is focused and invalid
::-webkit-input-placeholder is to style placeholder text in the input.
form__label
The class itself is not that special if you take a look at it but I still want to go over since this is the place animation is happening.
Initially I want to hide the label but want to display via animation once the user types in the input. How do I do that?
&__input:placeholder-shown is the pseudo class when placeholder text in the input is shown – nothing is typed yet. I need to use the class because I want to animate the label only when input is focused and something is typing. For this, you can use a sibling selector.
form__label is a direct sibling of the input so the line &__input:placeholder-shown + &__label will select the label when nothing is in the input. Since I am hiding everything, I set opacity to 0, and visibility is hidden to make it completely gone.
Once the user types something, then &__input:placeholder-shown + &__label is not selected and label is the only effective one. That’s how the animation is happening. Please note that + is the one making sibling selector. It only looks for direct sibling which is very sensitive to the order of the elements.
How to build a css component for radio button?
You cannot style radio button directly. Then, how can I apply CSS style? You can hide the original button and create a CSS component to look like radio button. It’s okay to hide the original radio button because it will still be selected thanks to form-group and the matching label.
If you look at &__radio-button, it has all the styles to make it look like an outline button. I had to use absolute position to place the button nicely. Please note that parent class also needs to specify the position, either relative or absolute.
Now, how do I create another circle inside the button outline when it’s clicked? We can use :checked property.
&__radio-input:checked ~ &__radio-label &__radio-button::after means once radio input is selected :checked will be enabled then look for a sibling called form__radio-label which is just a sibling (+ direct sibling, ~ general sibling, you could have used + here but just wanted to show ~ too) then selects ::after pseudo elements. Please note that initial opacity is 0 for ::after but if clicked, opacity now is 1 which creates another circle.