Often we can improve the user experience by grouping related fields, such as first and last name. In these cases, it's appropriate to include <fieldset>
. We'll first learn how to semantically create the fieldset HTML. Then, we'll set up a series of scalable classes that are flexible for various layout configurations. The layout classes will also be intrinsically responsive - meaning, they will respond to their parent container or the viewport without the use of media queries!
Instructor: [0:00] Now that we have our main field styles consistent across browsers, it's time to create some styles to provide some layout options. Let's create the new project file fieldset.njk. We'll provide it a title and then navigate to it in our local server.
[0:17] It's quite common that you may have fields that you would like to associate, such as first name and last name. A semantic way to produce this association is by using a fieldset. By using the convention we've set up previously for text inputs, let's add a first name and last name field.
[0:34] The border is the native browser styling for fieldset. Before we get into styling, we need to add one more HTML element, which is a legend. The legend serves as the label for the fieldset as a whole group. In this instance, we'll label the fieldset "Your name." For users of assistive tech, when they focus on the first field, most often they will also be read the legend value, as well.
[0:57] As you can see, native browser styling has some pretty strong opinions on how fieldsets should be displayed. The first task is to reset the fieldset and legend. Within our form reset stylesheet, we'll reset the basic box properties and including setting border to none on the fieldset. On save, we have successfully removed those native stylings across browsers.
[1:19] We will be creating a variety of classes to assist us in our form layout. We will call our Sass file form-layout and go ahead and add that as the last item within our main Sass file. The first improvement we'll make is to emphasize the legend by increasing its font size, setting it to the bold way and adding a bit of bottom margin.
[1:40] Now it's time to create our first new class. Because our goal is for the first and last name to line up next to each other, let's nest a div with the class of form-row, and this will wrap both of the form-groups.
[1:53] Within our new form-row class, we're going to define display: grid, with the grid gap of 2rem. In order to set up our columns, we'll use grid-template-columns and then the repeat function with the keyword of autofit to allow the grid algorithm to automatically add more columns as needed, rather than defining explicitly a number.
[2:17] Then set the minmax. For this, we're going to need to define a new local custom variable that we'll call field-column-width. We'll set that as a default to 20ch. We'll use that as the minimum allowed value in the minmax and set the max allowed value to 1fr. On save, we see that the two form-groups have aligned next to each other.
[2:42] Clearly, we need to also resolve our previously defined margin when the form-row class is in use. We'll apply a margin of for form-groups because they are instead relying on the grid-gap value to provide space. The magic of the autofit in combination with the minmax value is that on resize of our viewport, once the form-field's hit that minimum of 20ch, grid drops them down to their own unique rows.
[3:13] We can see one additional fix we need is to allow our form-fields to expand the full width that's available within their column. On the form-field class, we'll define a width of 100%. The way our grid-template-columns function works is that all of the columns will remain equal width, and they will also drop at the same time.
[3:37] Let's consider an alternate fieldset type, which is an address configuration. I've gone ahead and wrapped our various groups with the form-row where address gets its own, and city, state, and ZIP are wrapped in a form-row as well. Here you can see that because 3 across would be greater than the width allowed due to the minimum requirement, our ZIP has dropped to its own row.
[4:03] We also have some spacing issues. Let's resolve those first. Let's define that a form-row that follows another should receive a margin-top of 2rem. Then, outside our form-row class, let's define that a fieldset that falls another should also receive a margin-top of 2em. Now we can look at how to adjust our city, state, and ZIP so that when there is enough container width, the ZIP remains on the same line.
[4:27] The other expectation is that ZIP does not necessarily need to be a long field, since we're only expecting a handful of characters. On the form-row containing those fields, let's add a modifier to our form-row of mixed, indicating that we will allow mixed widths within this row.
[4:48] Within our new mixed modifier row, we're going to switch our display from grid to flexbox and we're going to be ultra-modern by using the gap property. A challenge you may face is having to support this width in older browsers until gap is fully supported for the majority of users.
[5:07] You may have to alter this solution to use for example margin. With that in place, since we've removed the width supplied by the grid, we already see an adjustment on those fields.
[5:18] To replace the behavior grid was supplying for the columns, we will create a new rule for our form-group and use the flex shorthand to allow form-group items to both grow and shrink. For the flex-basis value, we'll reuse our custom property for field column-width, which will act similar to the min part of the minmax function used on grid.
[5:41] On save, you see that update has occurred, ZIP has been dropped to its new row because keeping it on the same row as city and state would have squeezed it below that minimum allowed width provided by field column-width. Due to flex-grow, it has then been allowed to take up the entire width of its new row.
[5:59] Recall that part of the goal of our mixed rule was that we could customize widths and in order to do that, we'll go back into our fieldset and add a new class for the form-group around the ZIP that will be form-group with a modifier of width: auto.
[6:14] For that rule, we will update the flex behavior and define that it cannot grow but it is allowed to shrink, and we'll update the critical flex spaces value to auto. On save, we see that it is no longer growing.
[6:28] However, we had previously defined a min-width rule for the form-field class. Within our width: auto, we'll add a rule that's attached to both our form-field class and our form-control class. This is going to adjust that max-width value. We're going to define a new custom property called field-width and provide a fallback when that's not defined, of .
[6:50] Let's save and see what's going to happen. On save, the ZIP has jumped back up to be aligned, and that max-width is currently falling back to , which essentially means it's shrinking to a smaller value as it can although it's still being impacted by our padding values. Let's use this field-width custom property and apply an inline style on our ZIP form-group.
[7:12] We'll define that field-width custom property value is 10ch. On save, you see that the ZIP has been allowed to grow to that value. If we test our behavior by reducing the viewport size, you'll see the city, state, ZIP layout dynamically changing across screen sizes, simply based on the variety of flex-basis values.
[7:37] The final field set arrangement that we want to consider is an instance such as a couple of radio buttons, which we want to align. That's already provided if we use our form-row class, but we want them to be allowed their own unique variable-width without the need to manually define it, such as we enabled for the ZIP field.
[7:56] For this, we'll create one final form role modifier which will be variable, meaning the form-groups will be allowed to take on their own variable with them. For this class, we will copy our display: flex values, and on save, that's all we had to do to achieve our desired behavior.
[8:13] In this lesson, we learned how to semantically create a fieldset that includes a legend. Then, we created our form-row class, that in its initial state used grid to create columns of equal width that were intrinsically responsive based on our minimum allowed width value.
[8:31] Then we continued by creating a couple of modifier classes that would allow a mixed variety of field widths that also leveraged an additional CSS custom property to enable defining a width per field. Finally, our variable class which allowed form-group elements to take on their own variable width.
[8:50] As can be seen by using the inspector device toolbar, our layout is intrinsically responsive across viewport sizes without the need for additional media queries, meaning the layouts are responsive to their parent container rather than the viewport itself.