{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreieotgak6iru23napeutjrrvzrqhk3dz5tt47f2apkbsr5adtak3ym",
    "uri": "at://did:plc:svkyjirwpd7ts4qgnzoqfcc2/app.bsky.feed.post/3moih3csvsrtk"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreiawp5nbvdadeidetjlof7toa5x3fmll6o6t2i4727talxle5mtd4u"
    },
    "mimeType": "image/png",
    "size": 42558
  },
  "description": "I really really love the GOV.UK design system, and the Frontend framework that enables it GOV.UK Frontend. It's an absolute tour de force of a design system tested on 68.35 million people1. It's battle hardened, robust and accessible. Essentially it's the gold standard for common components and form design. It follows Government Design Principles and...",
  "path": "/2025/01/29/hello-gov/",
  "publishedAt": "2025-01-29T15:40:09.000Z",
  "site": "at://did:plc:svkyjirwpd7ts4qgnzoqfcc2/site.standard.publication/3mhpwfentz6lr",
  "tags": [
    "Code"
  ],
  "textContent": "I really really love the GOV.UK design system, and the Frontend framework that enables it GOV.UK Frontend. It’s an absolute tour de force of a design system tested on 68.35 million people1. It’s battle hardened, robust and accessible. Essentially it’s the gold standard for common components and form design. It follows Government Design Principles and GOV.UK Service Manual. As such it’s often a no brainer for me including it on WordPress builds. The simplest approach would be to grab the compiled CSS and JS from their latest release (or via npm), pop it into our wrapper and off we go. The bundles are an impressive 118KB for the CSS and 42.7KB for the JS (minified). But we can also tailor it for our specific needs as well. In this post I’ll be covering how we can set up GOV.UK Frontend with a smaller footprint and integrate it into Gravity Forms one of the leading Forms plugins for WordPress. The post is based on version 5.8.0; releases are pretty regular from the GOV.UK team. Init After adding the core JS, the only extra thing that’s required for any JS enhanced components is we need to conditionally add a class is JS is enabled by the user. This allows the system to progressively enhance components with JavaScript. <script> document.body.className += ' js-enabled' + ('noModule' in HTMLScriptElement.prototype ? ' govuk-frontend-supported' : ''); </script> and in our footer.php we need to initialise govuk-frontend. <script type=\"module\"> if (!window._govukFrontendInitialized) { import('<?php echo get_template_directory_uri(); ?>/dist/js/govuk-frontend-5.8.0.min.js') .then(({ initAll }) => { initAll(); window._govukFrontendInitialized = true; }) .catch(error => { console.error('Error loading govuk-frontend:', error); }); } </script> Sass So far so easy. But I don’t always want the full stack for every project. I do tend to include the minified JS as at 42KB as I feel this is an acceptable payload. But we definitely don’t always need every component, as such I usually set up my Sass files so I only include what I want (after installing govuk-frontend via npm). Below is my default. Note we need to redeclare a few variables to override the GOV.UK defaults. For the most part I am only including form stylings, we can add additional components as a when we need them. //// /// GOV.UK Frontend - https://frontend.design-system.service.gov.uk/ //// //Variables - need to be declared here to take effect $govuk-include-default-font-face: false; $govuk-focus-colour: #FFDD00; $govuk-button-background-colour: #0c6f3e; $govuk-button-text-colour: #FFFFFF; $govuk-font-family: system-ui, sans-serif; // Custom build // 'Base' includes everything from settings, tools and helpers. Nothing // in the base outputs any CSS. @import \"../../../node_modules/govuk-frontend/dist/govuk/base\"; // Basic content styles for typography, links etc. Approximately 10% of // the CSS output if you include everything. @import \"../../../node_modules/govuk-frontend/dist/govuk/core/index\"; // Objects include things like the page template, grid and form groups. // Approximately 5% of the CSS output if you include everything. @import \"../../../node_modules/govuk-frontend/dist/govuk/objects/index\"; // The components themselves - try to only include the components you // are using in your project. Approximately 70% of the CSS output if // you include everything. @import \"../../../node_modules/govuk-frontend/dist/govuk/components/accordion/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/breadcrumbs/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/button/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/checkboxes/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/details/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/error-message/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/error-summary/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/fieldset/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/input/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/label/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/notification-banner/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/pagination/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/radios/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/select/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/skip-link/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/textarea/index\"; @import \"../../../node_modules/govuk-frontend/dist/govuk/components/warning-text/index\"; // Utilities, for example govuk-clearfix or govuk-visually-hidden. // Approximately 1% of the CSS output if you include everything. @import \"../../../node_modules/govuk-frontend/dist/govuk/utilities/index\"; // Overrides, used to adjust things like the amount of spacing on an // element. Override classes always include `-!-` in the class name. // Approximately 15% of the CSS output if you include everything. @import \"../../../node_modules/govuk-frontend/dist/govuk/overrides/index\"; Gravity forms This gives us a great foundation to work from. Now, given we are mostly using forms, we need to make sure this works with our chosen Form builder. My go to is Gravity Forms. However the markup for a Gravity Form and GOV.UK Form component is different. Here is GOV.UK’s for a text input: <div class=\"govuk-form-group\"> <h1 class=\"govuk-label-wrapper\"> <label class=\"govuk-label govuk-label--l\" for=\"event-name\"> What is the name of the event? </label> </h1> <input class=\"govuk-input\" id=\"event-name\" name=\"eventName\" type=\"text\"> </div> And Gravity Forms <div class=\"gfield gfield--type-text\" data-js-reload=\"field_1_1\"> <label class=\"gfield_label gform-field-label\" for=\"input_1_1\"> What is the name of the event? </label> <div class=\"ginput_container ginput_container_text\"> <input name=\"input_1\" id=\"input_1_1\" type=\"text\" value=\"\" class=\"large\" aria-invalid=\"false\"> </div> </div> So it’s a bit different, and so we’ll have to do some mapping of styles. Also Gravity has it’s own theme, Orbital which I sort of turn off (Forms >> Settings >> Default Form Theme >> Gravity Forms 2.5 theme) and disable the core CSS via our functions.php add_filter('gform_disable_form_theme_css', '__return_true'); My general approach is to re-style core form elements around GOV.UK core styling, using my own custom variables from theme.json. For example: input[type=\"text\"], input[type=\"email\"], input[type=\"tel\"], input[type=\"search\"], input[type=\"url\"], textarea { font-size: 1rem; line-height: var(--wp--custom--typography--line-height--input); width: 100%; height: var(--wp--custom--height--input); margin: 0; padding: var(--wp--custom--spacing--xs); background-color: transparent; border: 2px solid var(--wp--custom--color--text, currentColor); border-radius: 0; appearance: none; &:focus-visible { outline: 0.2em solid var(--wp--preset--color--focus); outline-offset: 0; box-shadow: inset 0 0 0 2px; background-color: transparent; } } select { width: 100%; height: var(--wp--custom--height--input); line-height: var(--wp--custom--typography--line-height--small); border: 2px solid var(--wp--custom--color--text, currentColor); border-radius: 0; padding: 0 0.2em; font: inherit; //Inherit font family, size, and line height letter-spacing: inherit; word-spacing: inherit; margin: 0; padding: 0.2em; -moz-appearance: menulist; -webkit-appearance: menulist; appearance: menulist; &:focus-visible { outline: 0.15em solid var(--wp--preset--color--focus); box-shadow: 0 0 0.2em var(--wp--preset--color--focus); } } input[type=\"submit\"] { @extend %dgwltd-button; cursor: pointer; -webkit-appearance: none; &:hover { background-color: var(--wp--preset--color--secondary); } } It’s a big of double bagging, but it gives us a good basis to then go and target specific Gravity Forms classes for other elements, such as checkboxes and radio buttons. For example: .gfield_checkbox { input[type=\"checkbox\"] { cursor: pointer; z-index: 1; width: 40px; height: 40px; min-width: 40px; margin: 0 0 0 -10px; opacity: 0; } label:after { content: \"\"; position: absolute; top: 15px; left: 6px; width: 12px; height: 6.5px; transform: rotate(-45deg); border: solid; border-width: 0 0 3px 3px; border-top-color: transparent; opacity: 0; background: transparent; } input[type=\"checkbox\"]:focus-visible + label::before { border-width: 4px; box-shadow: 0 0 0 3px var(--wp--preset--color--focus); } input[type=\"checkbox\"]:checked + label:after { opacity: 1; } } The full forms.scss file can be seen here. Errors Gravity Forms has its own validation system, so we’ll need to consider Error messages and and Error Summary Error messages can be mostly handled via CSS: .gform_wrapper.gform_validation_error { .gfield_error { display: flex; flex-direction: column; margin-inline-start: 0; padding-left: var(--wp--custom--spacing--sm); border-left: 3px solid var(--wp--custom--color--error); //Order of fields .gfield_label { order: 1; } .gfield_description { order: 2; margin-block-end: var(--wp--custom--spacing--sm); } .ginput_container { order: 3; } input, textarea { border-color: var(--wp--custom--color--error); &:focus-visible { outline: 0.1em solid var(--wp--custom--color--error); } } } However, the Error Summary needs a little more attention, for this we need to extend Gravity Forms to include an Error Summary above the form summarise any errors a user has made. If we set a Custom Validation Message in the Gravity field settings this will display in this component, otherwise it will simply be the default ‘This field is required’. If the user clicks on the error they will be taken to that input with the corresponding ID To extend Gravity Forms to show an Error Summary we can add this PHP to our functions.php // Include field validation errors add_filter( 'gform_validation_message', function ( $message, $form ) { if ( gf_upgrade()->get_submissions_block() ) { return $message; } $message = '<div class=\"govuk-error-summary\" aria-labelledby=\"error-summary-title\" role=\"alert\" tabindex=\"-1\" data-module=\"govuk-error-summary\">'; $message .= '<h2 class=\"govuk-error-summary__title\" id=\"error-summary-title\">There is a problem</h2>'; $message .= '<div class=\"govuk-error-summary__body\">'; $message .= '<ul class=\"govuk-list govuk-error-summary__list\">'; foreach ( $form['fields'] as $field ) { if ( $field->failed_validation ) { $message .= sprintf( '<li><a href=\"#field_%s_%s\">%s</a></li>', $field->formId, $field->id, $field->validation_message ); } } $message .= '</ul></div></div>'; return $message; }, 10, 2 ); Put all together we get: There is a bunch more stuff I’ve done and we could also do, but this allows me a pretty solid basis to build Gravity Forms in WordPress.",
  "title": "Hello, GOV",
  "updatedAt": "2025-01-29T15:43:30.000Z"
}