From d6e38b14828f997f243654c66c29ddee531343ea Mon Sep 17 00:00:00 2001 From: owenatgov Date: Wed, 16 Aug 2023 11:28:00 +0100 Subject: [PATCH 1/3] Refactor positioning of radios and checkboxes --- .../govuk/components/checkboxes/_index.scss | 105 ++++++++-------- .../src/govuk/components/radios/_index.scss | 117 +++++++++--------- 2 files changed, 108 insertions(+), 114 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss b/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss index d64a087530..299c0c6850 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss @@ -4,21 +4,18 @@ @import "../label/index"; @include govuk-exports("govuk/component/checkboxes") { - $govuk-touch-target-size: 44px; + $govuk-touch-target-gutter: 4px; $govuk-checkboxes-size: 40px; + $govuk-touch-target-size: ($govuk-checkboxes-size + $govuk-touch-target-gutter); $govuk-small-checkboxes-size: 24px; $govuk-checkboxes-label-padding-left-right: govuk-spacing(3); + $govuk-checkbox-check-horizontal-position: 10px; .govuk-checkboxes__item { - display: block; + display: flex; + flex-wrap: wrap; position: relative; - - min-height: $govuk-checkboxes-size; - margin-bottom: govuk-spacing(2); - padding-left: $govuk-checkboxes-size; - - clear: left; } .govuk-checkboxes__item:last-child, @@ -27,39 +24,42 @@ } .govuk-checkboxes__input { - $input-offset: ($govuk-touch-target-size - $govuk-checkboxes-size) / 2; - - position: absolute; - - z-index: 1; - top: $input-offset * -1; - left: $input-offset * -1; - width: $govuk-touch-target-size; height: $govuk-touch-target-size; margin: 0; - opacity: 0; - cursor: pointer; } .govuk-checkboxes__label { - display: inline-block; + display: flex; + flex-wrap: wrap; + align-items: center; + + // Ensure that the width of the label is never more than the width of the + // container minus the input width minus the padding on either side of + // the label. This prevents the label from going onto the next line due to + // __item using flex-wrap because we want hints on a separate line. + max-width: calc(100% - (($govuk-checkboxes-label-padding-left-right * 2) + $govuk-touch-target-size)); margin-bottom: 0; - padding: 8px $govuk-checkboxes-label-padding-left-right govuk-spacing(1); + padding: (govuk-spacing(1) + $govuk-border-width-form-element) govuk-spacing(3); cursor: pointer; // remove 300ms pause on mobile touch-action: manipulation; } + // to account for when there are multiple children in a label + .govuk-checkboxes__label * { + width: 100%; + } + // [ ] Check box .govuk-checkboxes__label::before { content: ""; box-sizing: border-box; position: absolute; - top: 0; - left: 0; + top: ($govuk-touch-target-gutter / 2); + left: ($govuk-touch-target-gutter / 2); width: $govuk-checkboxes-size; height: $govuk-checkboxes-size; border: $govuk-border-width-form-element solid currentcolor; @@ -73,29 +73,37 @@ .govuk-checkboxes__label::after { content: ""; box-sizing: border-box; - position: absolute; - top: 11px; - left: 9px; + + // Use "magic numbers" to define shape and position of check mark because + // the complexity of the shape makes it difficult to calculate dynamically. + top: 13px; + left: $govuk-checkbox-check-horizontal-position; width: 23px; height: 12px; - transform: rotate(-45deg); border: solid; border-width: 0 0 5px 5px; // Fix bug in IE11 caused by transform rotate (-45deg). // See: alphagov/govuk_elements/issues/518 border-top-color: transparent; - opacity: 0; - background: transparent; } .govuk-checkboxes__hint { display: block; + width: 100%; + margin-top: govuk-spacing(-1); padding-right: $govuk-checkboxes-label-padding-left-right; - padding-left: $govuk-checkboxes-label-padding-left-right; + padding-left: ($govuk-checkboxes-label-padding-left-right + $govuk-touch-target-size); + } + + // This is to bypass govuk-hint's specificity on hints following labels having + // a margin bottom of 10px (govuk-spacing(2)). Because checkboxes are flexbox, + // the margin doesn't collapse so we have to do this manually. + .govuk-label:not(.govuk-label--m):not(.govuk-label--l):not(.govuk-label--xl) + .govuk-checkboxes__hint { + margin-bottom: 0; } // Focused state @@ -182,14 +190,9 @@ .govuk-checkboxes--small { $input-offset: ($govuk-touch-target-size - $govuk-small-checkboxes-size) / 2; - $label-offset: $govuk-touch-target-size - $input-offset; .govuk-checkboxes__item { - @include govuk-clearfix; - min-height: 0; margin-bottom: 0; - padding-left: $label-offset; - float: left; } // Shift the touch target into the left margin so that the visible edge of @@ -202,30 +205,23 @@ // ▲┆└─ Check box pseudo element, aligned with margin // └─── Touch target (invisible input), shifted into the margin .govuk-checkboxes__input { - left: $input-offset * -1; + margin-left: $input-offset * -1; } - // Adjust the size and position of the label. - // - // Unlike larger checkboxes, we also have to float the label in order to - // 'shrink' it, preventing the hover state from kicking in across the full - // width of the parent element. .govuk-checkboxes__label { - margin-top: -2px; - padding: 13px govuk-spacing(3) 13px 1px; - float: left; - - @include govuk-media-query($from: tablet) { - padding: 11px govuk-spacing(3) 10px 1px; - } + // Create a tiny space between the small checkbox hover state so that it + // doesn't clash with the label + padding-left: 1px; } // [ ] Check box // // Reduce the size of the check box [1], vertically center it within the // touch target [2] + // Left here is 0 because we've shifted the input into the left margin .govuk-checkboxes__label::before { - top: $input-offset - $govuk-border-width-form-element; // 2 + top: $input-offset; // 2 + left: 0; width: $govuk-small-checkboxes-size; // 1 height: $govuk-small-checkboxes-size; // 1 } @@ -234,8 +230,11 @@ // // Reduce the size of the check mark and re-align within the checkbox .govuk-checkboxes__label::after { - top: 15px; - left: 6px; + top: 17px; + + // Horizontal position is just the normal sized left value accounting for + // the new width of the smaller checkbox + left: (16px - $govuk-checkbox-check-horizontal-position); width: 12px; height: 6.5px; border-width: 0 0 3px 3px; @@ -250,16 +249,14 @@ // (If you do use them, they won't look completely broken... but seriously, // don't use them) .govuk-checkboxes__hint { - padding: 0; - clear: both; + padding-left: ($govuk-small-checkboxes-size + $input-offset); } // Align conditional reveals with small checkboxes .govuk-checkboxes__conditional { $margin-left: ($govuk-small-checkboxes-size / 2) - ($conditional-border-width / 2); margin-left: $margin-left; - padding-left: $label-offset - ($margin-left + $conditional-border-width); - clear: both; + padding-left: ($govuk-touch-target-size - $input-offset) - ($margin-left + $conditional-border-width); } // Hover state for small checkboxes. diff --git a/packages/govuk-frontend/src/govuk/components/radios/_index.scss b/packages/govuk-frontend/src/govuk/components/radios/_index.scss index 6b2e612fc3..dc9733237a 100644 --- a/packages/govuk-frontend/src/govuk/components/radios/_index.scss +++ b/packages/govuk-frontend/src/govuk/components/radios/_index.scss @@ -4,8 +4,9 @@ @import "../label/index"; @include govuk-exports("govuk/component/radios") { - $govuk-touch-target-size: 44px; + $govuk-touch-target-gutter: 4px; $govuk-radios-size: 40px; + $govuk-touch-target-size: ($govuk-radios-size + $govuk-touch-target-gutter); $govuk-small-radios-size: 24px; $govuk-radios-label-padding-left-right: govuk-spacing(3); // When the default focus width is used on a curved edge it looks visually smaller. @@ -13,15 +14,10 @@ $govuk-radios-focus-width: $govuk-focus-width + 1px; .govuk-radios__item { - display: block; + display: flex; + flex-wrap: wrap; position: relative; - - min-height: $govuk-radios-size; - margin-bottom: govuk-spacing(2); - padding-left: $govuk-radios-size; - - clear: left; } .govuk-radios__item:last-child, @@ -30,43 +26,44 @@ } .govuk-radios__input { - $input-offset: ($govuk-touch-target-size - $govuk-radios-size) / 2; - - position: absolute; - - z-index: 1; - top: $input-offset * -1; - left: $input-offset * -1; - width: $govuk-touch-target-size; height: $govuk-touch-target-size; margin: 0; - opacity: 0; - cursor: pointer; } .govuk-radios__label { - display: inline-block; + display: flex; + flex-wrap: wrap; + align-items: center; + + // Ensure that the width of the label is never more than the width of the + // container minus the input width minus the padding on either side of + // the label. This prevents the label from going onto the next line due to + // __item using flex-wrap because we want hints on a separate line + max-width: calc(100% - ($govuk-radios-label-padding-left-right + $govuk-touch-target-size + govuk-spacing(3))); margin-bottom: 0; - padding: 8px $govuk-radios-label-padding-left-right govuk-spacing(1); + padding: (govuk-spacing(1) + $govuk-border-width-form-element) govuk-spacing(3); cursor: pointer; // remove 300ms pause on mobile touch-action: manipulation; } + // to account for when there are multiple children in a label + .govuk-radios__label * { + width: 100%; + } + // ( ) Radio ring .govuk-radios__label::before { content: ""; box-sizing: border-box; position: absolute; - top: 0; - left: 0; - + top: ($govuk-touch-target-gutter / 2); + left: ($govuk-touch-target-gutter / 2); width: $govuk-radios-size; height: $govuk-radios-size; - border: $govuk-border-width-form-element solid currentcolor; border-radius: 50%; background: transparent; @@ -77,16 +74,19 @@ // We create the 'button' entirely out of 'border' so that they remain // 'filled' even when colours are overridden in the browser. .govuk-radios__label::after { - content: ""; + $radio-button-size: govuk-spacing(2); + content: ""; position: absolute; - top: govuk-spacing(2); - left: govuk-spacing(2); + // Positioned by getting half the touch target, so we have the centre of the + // input, and then moving back by the button's border width, thus positioning + // the centre of the button in the centre of the input. + top: (($govuk-touch-target-size / 2) - $radio-button-size); + left: (($govuk-touch-target-size / 2) - $radio-button-size); width: 0; height: 0; - - border: govuk-spacing(2) solid currentcolor; + border: $radio-button-size solid currentcolor; border-radius: 50%; opacity: 0; background: currentcolor; @@ -94,8 +94,17 @@ .govuk-radios__hint { display: block; + width: 100%; + margin-top: govuk-spacing(-1); padding-right: $govuk-radios-label-padding-left-right; - padding-left: $govuk-radios-label-padding-left-right; + padding-left: ($govuk-radios-label-padding-left-right + $govuk-touch-target-size); + } + + // This is to bypass govuk-hint's specificity on hints following labels having + // a margin bottom of 10px (govuk-spacing(2)). Because radios are flexbox, + // the margin doesn't collapse so we have to do this manually. + .govuk-label:not(.govuk-label--m):not(.govuk-label--l):not(.govuk-label--xl) + .govuk-radios__hint { + margin-bottom: 0; } // Focused state @@ -140,12 +149,12 @@ .govuk-radios--inline { @include govuk-media-query($from: tablet) { - @include govuk-clearfix; + display: flex; + flex-wrap: wrap; + align-items: flex-start; .govuk-radios__item { margin-right: govuk-spacing(4); - float: left; - clear: none; } } } @@ -198,14 +207,9 @@ .govuk-radios--small { $input-offset: ($govuk-touch-target-size - $govuk-small-radios-size) / 2; - $label-offset: $govuk-touch-target-size - $input-offset; .govuk-radios__item { - @include govuk-clearfix; - min-height: 0; margin-bottom: 0; - padding-left: $label-offset; - float: left; } // Shift the touch target into the left margin so that the visible edge of @@ -218,30 +222,23 @@ // ▲┆└─ Radio pseudo element, aligned with margin // └─── Touch target (invisible input), shifted into the margin .govuk-radios__input { - left: $input-offset * -1; + margin-left: $input-offset * -1; } - // Adjust the size and position of the label. - // - // Unlike larger radios, we also have to float the label in order to - // 'shrink' it, preventing the hover state from kicking in across the full - // width of the parent element. .govuk-radios__label { - margin-top: -2px; - padding: 13px govuk-spacing(3) 13px 1px; - float: left; - - @include govuk-media-query($from: tablet) { - padding: 11px govuk-spacing(3) 10px 1px; - } + // Create a tiny space between the small radio hover state so that it + // doesn't clash with the label + padding-left: 1px; } // ( ) Radio ring // // Reduce the size of the control [1], vertically centering it within the // touch target [2] + // Left here is 0 because we've shifted the input into the left margin .govuk-radios__label::before { - top: $input-offset - $govuk-border-width-form-element; // 2 + top: $input-offset; // 2 + left: 0; width: $govuk-small-radios-size; // 1 height: $govuk-small-radios-size; // 1 } @@ -250,9 +247,12 @@ // // Reduce the size of the 'button' and center it within the ring .govuk-radios__label::after { - top: 15px; - left: 7px; - border-width: 5px; + $radio-button-size: govuk-spacing(1); + + // The same calculation as normal radio buttons but reduce the border width + top: (($govuk-touch-target-size / 2) - $radio-button-size); + left: ((($govuk-touch-target-size / 2) - $radio-button-size) - $input-offset); + border-width: $radio-button-size; } // Fix position of hint with small radios @@ -264,17 +264,14 @@ // (If you do use them, they won't look completely broken... but seriously, // don't use them) .govuk-radios__hint { - padding: 0; - clear: both; - pointer-events: none; + padding-left: ($govuk-small-radios-size + $input-offset); } // Align conditional reveals with small radios .govuk-radios__conditional { $margin-left: ($govuk-small-radios-size / 2) - ($conditional-border-width / 2); margin-left: $margin-left; - padding-left: $label-offset - ($margin-left + $conditional-border-width); - clear: both; + padding-left: ($govuk-touch-target-size - $input-offset) - ($margin-left + $conditional-border-width); } .govuk-radios__divider { From c44a85e6d333686eb0f970ea3c87dcd7abe81bc0 Mon Sep 17 00:00:00 2001 From: owenatgov Date: Wed, 23 Aug 2023 16:58:11 +0100 Subject: [PATCH 2/3] Replace flex display of labels with align-items: center This means that the label content is still centred against the input whilst not interfering with inline/block element dynamics within the label --- .../src/govuk/components/checkboxes/_index.scss | 9 +-------- .../src/govuk/components/radios/_index.scss | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss b/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss index 299c0c6850..bc656e69ce 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss @@ -32,9 +32,7 @@ } .govuk-checkboxes__label { - display: flex; - flex-wrap: wrap; - align-items: center; + align-self: center; // Ensure that the width of the label is never more than the width of the // container minus the input width minus the padding on either side of @@ -48,11 +46,6 @@ touch-action: manipulation; } - // to account for when there are multiple children in a label - .govuk-checkboxes__label * { - width: 100%; - } - // [ ] Check box .govuk-checkboxes__label::before { content: ""; diff --git a/packages/govuk-frontend/src/govuk/components/radios/_index.scss b/packages/govuk-frontend/src/govuk/components/radios/_index.scss index dc9733237a..4e017d4cc8 100644 --- a/packages/govuk-frontend/src/govuk/components/radios/_index.scss +++ b/packages/govuk-frontend/src/govuk/components/radios/_index.scss @@ -34,9 +34,7 @@ } .govuk-radios__label { - display: flex; - flex-wrap: wrap; - align-items: center; + align-self: center; // Ensure that the width of the label is never more than the width of the // container minus the input width minus the padding on either side of @@ -50,11 +48,6 @@ touch-action: manipulation; } - // to account for when there are multiple children in a label - .govuk-radios__label * { - width: 100%; - } - // ( ) Radio ring .govuk-radios__label::before { content: ""; From 2486fedd422bc5c0ae55d8ae6c83847ff9a424dc Mon Sep 17 00:00:00 2001 From: Owen Jones Date: Tue, 2 Jan 2024 13:14:59 +0000 Subject: [PATCH 3/3] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b79f35457..efa47b01e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ We've made fixes to GOV.UK Frontend in the following pull requests: - [#4157: Dynamically position text within input prefixes and suffixes](https://github.com/alphagov/govuk-frontend/pull/4157) - [#4150: Header menu button position refactor](https://github.com/alphagov/govuk-frontend/pull/4150) +- [#4093: Refactor positioning of radios and checkboxes](https://github.com/alphagov/govuk-frontend/pull/4093) ## 5.0.0 (Breaking release)