verbb / formie

The most user-friendly forms plugin for Craft CMS.
Other
96 stars 73 forks source link

Improve the default responsive behaviour of multi-column forms #1718

Closed MoritzLost closed 3 months ago

MoritzLost commented 9 months ago

What are you trying to do?

By default, form rows use display: flex and form fields use flex: 1 (implicitly 1 1 0%). This makes all form fields have the same width by default, and allows them to shrink down indefinitely. This means columns never wrap into multiple columns, even if the container/viewport grows very short.

Screenshot 2024-02-07 at 15 10 02

This means that multi-column layouts basically require custom styling, since those fields are way too small to be usable.

What's your proposed solution?

Formie could provide better default styling. The fields should have a reasonable minimum width and wrap into multiple rows if the container gets too small. For example, using something like flex: 1 0 15rem on the fields would ensure that fields can never go below 15rem in width. One downside of this approach is that fields may wrap in undesirable ways. For example, a three-column layout might wrap to a second row, with two fields in the first row and the third field in the second row.

Another option would be to use a fixed minimum width for multi-column layout and stack everything on top of each other below that breakpoint. That's not ideal, but better than the current behaviour. However, this should not be done with a fixed breakpoint/media query, as the form could be rendered in a container that's smaller than the viewport. Some potential solutions to this issue:

Another option would be to ditch flex and use grid, and explicitly define grid columns based on the number of fields in a row. This would allow for more control over the wrapping behaviour. Though a grid-based solution will probably require container queries as well, if we want some amount of control over the wrapping behaviour. Support for those is actually reasonable for a progressive enhancement approach: https://caniuse.com/css-container-queries

The grid RAM pattern could also be used, but again, the wrapping behaviour is not great.

Whatever solution is chosen, it should also apply to fields with nested subfields, like the address field or the name field.

Additional context

No response

engram-design commented 9 months ago

Yep, I agree we need a mechanism to handle responsiveness a little better. It's a tricky line to tread, as we don't have to enforce breakpoints too much - even for set-and-forget forms where Formie's value is. Every site and use-case is different, so it's tricky to cover them all.

What we really don't want, is to put breakpoint management in the hands of clients or end-users editing a form. In my experience with novice users, this terminology only serves as noise and confusion - no matter how well explained.

But something needs to be done as a start, and to try and keep things as flexible as possible.

The second approach is probably my preference, but as you mention, I did get stuck on a fixed breakpoint not really being desirable. I'll investigate these two approaches and see what we get!

I'm probably going to shelve this for Formie 3 for Craft 5 as a breaking change. While it's probably something "urgent" that needs fixing, any solution is also probably going to mess up any overrides people are doing, and I'd rather be safe about this sort of change. But, Formie 3 is just on the horizon, so I'll be looking at this shortly.

MoritzLost commented 9 months ago

@engram-design Sounds great! I agree, users shouldn't have any options for responsive behaviour, that should be controlled by CSS.

I think I'm going with something like this for now:

--field-min-width: 15rem;

.fui-row {
    margin: 0;
    display: grid;
    gap: var(--fui-field-gutter);
    grid-template-columns: repeat(auto-fit, minmax(var(--field-min-width), 1fr));
}

.fui-field {
    padding: 0;
}

This is the RAM pattern. This works well since every row is in its own wrapper, so the grids can have different column counts. Using auto-fit creates as many columns as needed for the amount of fields inside the row, with each column (field) growing equally to take up any remaining space, similar to the existing flex behaviour. Important not to use auto-fill here, that would create additional empty rows if the space is large enough. When the space becomes too small to hold all columns given the gap and minimum width, columns start to wrap to multiple rows. Importantly, this is based on the container, not on the viewport.

It's also extensible – you can modify the --field-min-width based on the number of columns (#1719 is great for that!). For example, to allow more narrow columns in three or four column layouts. And it's easy to modify the column template for specific fields. For an address row with location and zip fields, we may want something like this:

grid-template-columns: minmax(15rem, 3fr) minmax(5rem, 1fr)

This gives a 3/1 ratio for the two fields.

The only tangible downside is that a three- or four-column layout may wrap to three or two columns, so it doesn't quite match the layout defined in the form builder. But that's always going to be the case for those layouts at smaller container widths, and I prefer that to having everything collapse to a single column layout at a specific width. The advantage over flex is that the fields that wrap in the next row won't grow to fill the entire row, but stay at the defined track size.

https://github.com/verbb/formie/assets/10146880/f19c887d-9a62-4d8c-84ee-7a9ac0de01f5

engram-design commented 9 months ago

I think the wrapping is absolutely inevitable, and as long as it's flexible and extensible by developers like yourself, I think that's where we'll end up sitting. At some point, 3/4 columns has to go on a new line, as it's otherwise unusable. Controlling that will be at the discretion of the developer, but Formie will provide sane defaults.

My only tiny concern in that the front-end JS/CSS needs to be as browser-supported as possible. I think since Formie 2 IE has EOL-ed, so it's probably not a massive concern, but I'll double check everything is satisfactory for mass-adoption.

I'll be sure to shoot through the proposed changed when we release the beta, if you want to provide feedback on the implementation before release.

Really appreciate your time and discussion here!

MoritzLost commented 9 months ago

@engram-design Looking forward to it!

Yeah, wrapping is inevitable. Sometimes this is unexpected for clients – especially when they sit at a tiny laptop with a low resolution and use the live preview, so there's like a 500px viewport. Then they are surprised that their layout options seemingly aren't working. But that can't really be helped, better to optimize for real users ^^

Flex and Grid are really well-supported now, only IE 11 doesn't support the current grid spec. If Formie doesn't support IE it should be fine, as long as it doesn't use any of the Grid Level 2 (Subgrid) or Level 3 (Masonry). We haven't supported IE for years … the official EOL was on June 15, 2022, so it should be fine to drop support for it.

MoritzLost commented 9 months ago

Just for future reference, my finished solution looks like this now:

.fui-row {
    margin: 0;
    display: grid;
    gap: var(--fui-field-gutter);
    grid-template-columns: repeat(auto-fit, minmax(var(--field-min-width, 15rem), 1fr));

    [data-fui-field-count='3'] {
        --field-min-width: 12rem;
    }

    [data-fui-field-count='4'] {
        --field-min-width: 10rem;
    }

    [data-fui-field-count='5'],
    [data-fui-field-count='6'],
    [data-fui-field-count='7'],
    [data-fui-field-count='8'] {
        --field-min-width: 8rem;
    }
}

.fui-row .fui-field {
    padding: 0;
    margin-bottom: 0;
}

This requires the data attributes with the field counts per row in #1719.

The minimum field width is reduced based on the number of fields in a row. This is consistent with the expected behaviour – if an editors groups four fields in one row, they're probably expecting short inputs. For example, this allows the individual inputs in a date fields (in dropdown mode) to shrink to a small size before wrapping, while rows with just two columns will wrap earlier. Tested this with a bunch of different fields and layouts and it's working really well.

engram-design commented 3 months ago

Updated in 3.0.0

darylknight commented 1 week ago

I just tried this and ran into responsive issues too - and tried to track what changed in 3.0.0 as I'm on 3.0.9. It looks like because I'm using themeConfig (specifically this: https://github.com/verbb/formie-theme-configs/blob/formie-3/tailwind/index.html), none of the responsiveness works because it removes the fui- classes.

Is there a straightforward way to keep the responsiveness (single column on mobile) while also using the themeConfig with resetClasses: true?

engram-design commented 1 week ago

The changes involve several handlings for the data-fui-field-count based on the fui-row class. See here.

If you're using Tailwind though, it's somewhat easier, as you can just define your responsive breakpoints, and using CSS grids. The Tailwind theme config is just an example after all.

Here's a responsive example using CSS Grids, based on the number of fields in a row (there will always be max 4 fields in a row), which responds reasonably well.

row: {
    attributes: {
        class: [
            'grid mb-4 gap-4 grid-cols-1',
            "{{ row.getFields(false, false) | length == 2 ? 'md:grid-cols-2' }}",
            "{{ row.getFields(false, false) | length == 3 ? 'md:grid-cols-2 lg:grid-cols-3' }}",
            "{{ row.getFields(false, false) | length == 4 ? 'md:grid-cols-2 xl:grid-cols-4' }}",
            "{{ row.getIsHidden() ? 'hidden' }}",
        ],
    },
},

field: {
    attributes: {
        class: 'flex-1',
    },
},
darylknight commented 1 week ago

This is wonderful, thank you very much!