Have you ever struggled with vertically aligning the content of your interactive elements? You're in good company. A lot of us do.
In this article, we'll look at how to center the content of buttons and input elements using line-height, padding, and flexbox.
So, what's our goal here?
We want to build a system where:
- the content of buttons and input elements is perfectly aligned
- buttons and input elements have the same height
- font-size, line-height, padding, and border determine the size of buttons and inputs*
*An alternative approach would be setting a fixed height (e.g., height: 40px;
) and a line-height equal to the height value (e.g., line-height: 40px;
) for all buttons and inputs. However, using padding (instead of a fixed height) is safer because the button will adapt to its content under all circumstances.
🧐 Need inspiration for your upcoming form components? Explore our gallery of form templates or browse the entire component library for more design ideas.
Basic button and input style #
The height and vertical alignment of buttons and inputs is determined by the combination of borders, padding, font-size, and line-height.
With that in mind, let's define the basic style of buttons and inputs:
<input class="form-control" type="text" value="Input element">
<button class="btn">Button</button>
<style>
.btn, .form-control {
/* reset user agent stylesheet */
background-color: transparent;
padding: 0;
border: 0;
border-radius: 0;
color: inherit;
appearance: none;
/* make sure properties affecting height have same value */
font-size: 1em;
line-height: 1.2;
padding: 0.5em var(--padding-x);
border-width: 2px;
border-style: solid;
}
/* button */
.btn {
display: inline-flex;
justify-content: center; /* center the content horizontally */
align-items: center; /* center the content vertically */
--padding-x: 1.2em;
border-color: transparent; /* hide button border */
}
/* input */
.form-control {
--padding-x: 0.5em;
}
</style>
Takeaway points from the above snippet:
- We set the display value of the buttons equal to inline-flex so that we can use the justify-content and align-items properties to center the content (particularly handy if you place an icon inside a button).
- We apply the same vertical padding, font-size, line-height, and border-width to buttons and inputs.
- Even tough we don't plan on adding a visible border to the buttons, we apply it anyway (with a transparent color) to make sure buttons and inputs have the same height.
- The line-height value needs to be slightly bigger than "1". If you use "1", the input elements won't accept it, and they'll be taller than the buttons. In our example, we're applying "1.2" (you could use "normal" if you prefer).
This basic style will ensure buttons and input elements have the same height and that their content is vertically aligned. You can modify their font-size (e.g., set a fixed font-size) or apply a different font-family, and it won't affect the alignment. After all, we're just pushing the content using padding and border.
You can also scale them up/down taking advantage of the Em units.
Real-world examples #
Let's see this technique in action with some real-world examples.
A typical case where you want to align a button with an input field is a newsletter form.
If you are collecting the user email only, the subscribe button can be placed right next to the email input field, as it is done in this newsletter component example.
<form class="grid gap-2xs">
<input class="form-control" aria-label="Email" type="email" placeholder="Email address">
<button class="btn btn--primary">Subscribe</button>
</form>
<style>
.btn, .form-control {
font-size: 1em;
line-height: 1.2;
padding-top: 0.5em;
padding-bottom: 0.5em;
border-radius: 0.375em;
}
.btn {
/* buttons custom style */
}
.form-control {
/* input custom style */
}
</style>
Use cases of this technique are not limited to newsletter forms.
Consider this repeater component. It can be used to clone and delete a collection of items.
Consider an account form where you want your users to be able to list all their social accounts. They should be able to add multiple social links, and delete them if too many were added. A delete button next to an input field does the trick; and clearly, they should be vertically aligned!
<ul class="grid gap-xs">
<li>
<div class="grid gap-xs">
<input class="form-control" type="text" name="user[0][socials]" id="user[0][socials]" placeholder="Account link">
<button class="btn btn--subtle" type="button">
<svg class="icon" viewBox="0 0 20 20">
<title>Remove item</title>
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<line x1="1" y1="5" x2="19" y2="5"/>
<path d="M7,5V2A1,1,0,0,1,8,1h4a1,1,0,0,1,1,1V5"/>
<path d="M16,8l-.835,9.181A2,2,0,0,1,13.174,19H6.826a2,2,0,0,1-1.991-1.819L4,8"/>
</g>
</svg>
</button>
</div>
</li>
</ul>
<style>
.btn, .form-control {
font-size: 1em;
line-height: 1.2;
padding-top: 0.5em;
padding-bottom: 0.5em;
border-radius: 0.375em;
}
.btn {
/* buttons custom style */
}
.form-control {
/* input custom style */
}
</style>
Exceptions #
There may be cases where we need to set a fixed height for our button/input elements, and the default line-height will break the alignment.
In such cases, we can remove the vertical padding and set the line-height equal to the height value.
Here's a workaround based on creating height utility classes that behaves slightly differently when applied to buttons and inputs:
.height-30, .height-40, .height-50 {
height: var(--height);
&.btn, &.form-control {
line-height: var(--height);
padding-top: 0;
padding-bottom: 0;
}
}
.height-30 {
--height: 30px;
}
.height-40 {
--height: 40px;
}
.height-50 {
--height: 50px;
}
Feedback? #
Do you use a different method you'd like to share? Let us know on Twitter!