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.