CSS – Build Cool Forms

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.