In this super-quick tutorial, we’ll walk through the process of creating an off-canvas feedback form. We won’t use any JavaScript to accomplish this, in fact, we’ll build it by taking advantage of the “CSS checkbox hack technique”. Sound interesting?
Here’s what we’ll be building:
Let’s get started!
1. Begin With the Page Markup
We’ll start with a checkbox along with its label and a form. Within the form, we’ll put some common form elements.
Keep in mind that the order here matters. First we’ll place the checkbox, then the label, and finally the form.
We’ll associate each form control with its label by setting the id
value equal to the label’s for
value:
Here’s the page markup:
<input type="checkbox" id="mycheckbox"><label for="mycheckbox" class="feedback-label">FEEDBACK</label><form class="form"><div><label for="fullname">Full Name</label><input type="text" id="fullname"></div><div><label for="email">Email</label><input type="email" id="email"></div><div><label for="comment">Comment</label><textarea id="comment"></textarea></div><div><button type="submit">Send</button></div></form>
2. Define Some Basic Styles
Now let’s define a few custom variables to give us our layout scheme plus some reset rules, especially for the form controls:
:root { --white: white; --gradient: linear-gradient(-45deg, #FFA600 0%, #FF5E03 50%); --form: #eeefef; --border-radius: 4px; --form-width: 500px; --form-mob-width: 320px; } * { padding: 0; margin: 0; box-sizing: border-box; } button, label { cursor: pointer; } label { display: block; } button, input, textarea { font-family: inherit; font-size: 100%; border: none; } textarea { resize: none; }
3. Style the Form Elements
Now onto the mechanics. As a first step, we’ll visually hide the checkbox:
[type="checkbox"] { position: absolute; left: -9999px; }
Then, we’ll do the following things:
- Set the checkbox’s label and form as fixed positioned elements. Additionally, we’ll position them in the center right corner of the page.
- Display the label’s text in vertical orientation.
- Hide the form off-canvas. By default, we’ll move it by 100% of its width to the right.
The corresponding styles are as follows:
/*CUSTOM VARIABLES HERE*/ .feedback-label, .form { position: fixed; top: 50%; right: 0; backface-visibility: hidden; } .feedback-label { transform-origin: top right; transform: rotate(-90deg) translate(50%, -100%); z-index: 2; } .form { width: var(--form-width); max-height: 90vh; transform: translate(100%, -50%); padding: 30px; overflow: auto; background: var(--form); z-index: 1; }
Next, we’ll add some straightforward styles to the form elements:
/*CUSTOM VARIABLES HERE*/ .feedback-label, .form input, .form textarea, .form button { border-radius: var(--border-radius); } .feedback-label, .form button { background: var(--gradient); color: var(--white); } .feedback-label:hover, .form button:hover { filter: hue-rotate(-45deg); } .feedback-label { padding: 5px 10px; border-radius: var(--border-radius) var(--border-radius) 0 0; } form div:not(:last-child) { margin-bottom: 20px; } form div:last-child { text-align: right; } .form input, .form textarea { padding: 0 5px; width: 100%; } .form button { padding: 10px 20px; width: 50%; max-width: 120px; } .form input { height: 40px; } .form textarea { height: 220px; }
Toggle Form
Each time we click on the checkbox’s label, the form should appear or disappear with a slide animation. To make that happen, we’ll take advantage of the :checked
pseudo-class, the subsequent-sibling selector (~
), and the adjacent sibling combinator (+
).
Here are the required styles:
/*CUSTOM VARIABLES HERE*/ [type="checkbox"]:checked + .feedback-label { transform: rotate(-90deg) translate(50%, calc((var(--form-width) + 100%) * -1)); } [type="checkbox"]:focus + .feedback-label { outline: 2px solid rgb(77, 144, 254); } [type="checkbox"]:checked ~ .form { transform: translate(0, -50%); } .feedback-label, .form { transition: all 0.35s ease-in-out; }
Let's discuss the first three styles above:
- The first selector looks for
.feedback-label
elements that immediately follow checked checkboxes. Here, instead of the+
selector, we could equally have used the more generic~
selector. It then smoothly moves those elements right after the form. Notice the Y value of thetranslate()
function which seems a bit chaotic. If you look at a previous rule this is initially-100%
. Now it should be-(form width + 100%)
. To force thecalc()
function to return a negative value, we’ll multiply the result of the target operation by -1. - The second selector looks for
.feedback-label
elements that immediately follow focused checkboxes. Again here, the~
selector would work as well. It then gives those elements an outline. This is useful for keyboard accessibility. Use the Tab key to move focus to the checkbox (label). Next, press the Spacebar key to toggle the form state. - The third selector looks for
.form
elements that follow (even not immediately) checked checkboxes. It then smoothly moves those elements into view by removing the X value of thetranslate()
function
4. Going Responsive
As a last (important) detail, we’ll specify a few rules for mobile screens. This doesn’t involve anything really drastic, we’ll just decrease the form width from 500px to 320px.
The responsive styles:
/*CUSTOM VARIABLES HERE*/ @media screen and (max-width: 600px) { body { font-size: 16px; } .form { padding: 15px; width: var(--form-mob-width); } form div:not(:last-child) { margin-bottom: 10px; } [type="checkbox"]:checked + .feedback-label { transform: rotate(-90deg) translate(50%, calc((var(--form-mob-width) + 100%) * -1)); } }
Conclusion
That’s it, folks! Without putting too much effort into this, we managed to implement a useful off-canvas feedback form. I hope you enjoyed this exercise and learned some new things which you can put into practice in the near future.
Let’s look again at what we built:
As always, thanks for reading!