Skip to content

Use custom properties to change button colours#7269

Open
romaricpascal wants to merge 5 commits into
add-interruption-panel--actions-optionfrom
spike-button-custom-properties
Open

Use custom properties to change button colours#7269
romaricpascal wants to merge 5 commits into
add-interruption-panel--actions-optionfrom
spike-button-custom-properties

Conversation

@romaricpascal

@romaricpascal romaricpascal commented Jul 9, 2026

Copy link
Copy Markdown
Member

This PR refactors buttons so they use custom properties to set the button colours for each variant.

Reduces selector duplication

This reduces the duplication of pseudo-selectors for each button state, as each variant now only need to assign the correct colour to 4 custom properties:

  • --_govuk-button-background-colour for the background of the button
  • --_govuk-button-text-colour for the text of the button
  • --_govuk-button-shadow-colour for the shadow of the button
  • --_govuk-button-hover-background-colour for the background when hovered.

Note

These custom properties are currently considered internal implementation details and not meant to be used by external code. This is hinted by the leading _ in their name.
It's likely that we will have to provide them as a public API at some point in the future, at which point we can decide on a final naming for the properties (which could likely be the same as now, without the _, but we'll need to check if fits the other components relying on custom properties). As this point as well, the button colours will likely become functional colours.

Enables change of default button styles inside components

This also enables components to redefine the colours of buttons within them without asking users (or us) to set a .govuk-button--<VARIANT> class on the buttons:

  • the Interruption panel can make .govuk-buttons inverse without needing to explicitely set .govuk-button--inverse on the elements
  • the File upload can make its pseudo-button secondary without setting it to .govuk-button--secondary and adjust its colours with less specific selectors (as well as no longer rely on duplicate calls to govuk-colour()).

If a component defines a new default for .govuk-button, using .govuk-button--<VARIANT> still allows to apply a variant to a specific button inside that component. The .govuk-button--<VARIANT> classes set the same custom --_govuk-button-<COLOUR-NAME> properties listed abot, and being set on the button itself, will take precedence over those cascading from a parent element.

Note

Given applying .govuk-button--<VARIANT> to any element would change the styles of the default buttons within it, we may want to consider renaming these classes .govuk-button-style--<VARIANT> and widen their use.

Use custom properties to store the colours of each variant

The PR moves from using Sass variables to using custom properties for storing the colours of each button variants. This was the simplest way to enable reuse of the colours, as some of the colours are set from the variables in the _settings.scss file.

This creates two sets of custom properties:

  • --_govuk-button-<COLOUR-NAME> to apply colours to a button
  • --_govuk-<VARIANT>-button-<COLOUR-NAME> to define the colours for a specific variant

Note

Same as earlier, these custom properties are considered internal for now. They may need to become public when looking at dark mode (eg. to adjust the secondary colour) or if we lean into white labelling at some point.

You may notice there's no variables for the "default" button styles. Until there's a clear demand/use for it, there's no reason to introduce extra properties for this where the --_govuk-button-<COLOUR-NAME> variables can fulfill that role. If needed this could be worked around with the following (albeit relying on setting internal custom properties):

:root {
  // Store the initial values of the `--govuk-button-<COLOUR-NAME>` in other variables
  // that can be referred to is a component changes the default button styles
  --_app-default-button-background-colour: var(--_govuk-button-background-colour);
  --_app-default-button-text-colour: var(--_govuk-button-text-colour);
  --_app-default-button-shadow-colour: var(--_govuk-button-shadow-colour);
  --_app-default-button-hover-background-colour: var(--_govuk-button-hover-background-colour);
}

.app-button--default {
  // Apply the stored variables to the button
  --_govuk-button-background-colour: var(--_app-default-button-background-colour);
  --_govuk-button-text-colour: var(--_app-default-button-text-colour);
  --_govuk-button-hover-background-colour: var(--_app-default-button-hover-background-colour);
  --_govuk-button-shadow-colour: var(--_app-default-button-shadow-colour);
}

Consistent application of colours through a new govuk-button-style mixin

Because configuring button colours rely on setting a set of 4 properties from consistently named custom properties, this can be encapsulated in a mixin that'll reduce the risks of typos and error if requesting a variant that's not defined.

Note

Like the variables, that mixin is kept internal for now, but could become part of our public Sass API in the future if there's demand.

Fixes #6483

romaricpascal and others added 5 commits July 9, 2026 18:18
Introduce local, private, custom properties defining the following colours of the Button component:
- background colour
- text colour
- hover background colour
- shadow colour

Use them in each variant rather than re-implementing pseudo-selectors for each state.

Co-authored-by: Nick Colley <2445413+NickColley@users.noreply.github.com>
Adds custom properties to stores the inverse button colours on `:root`
and let the Panel assign them to the relevant `--_govuk-button-...-colour` properties,
so that buttons don't need a `.govuk-button--inverse` modifier.

Co-authored-by: Nick Colley <2445413+NickColley@users.noreply.github.com>
By using custom properties, we can lighten up the markup of the button (removing `.govuk-button--secondary`),
as well as clear up a couple of selectors in the File upload's stylesheet.

This involves adding new custom properties on `:root` from the Button component to be used in the File upload.
While the file upload technically does not use the Button, it would be use in a form so would have a Button to submit anyways.

Co-authored-by: Nick Colley <2445413+NickColley@users.noreply.github.com>
To align with the inverse and secondary button colours, use custom properties to store the warning button colours.

This allows to remove the Sass variables storing colours, given all they'd do is be assigned to a custom property.
Thanks to consistent naming, applying colours to a button requires assigning `--_govuk-<VARIANT>-button-<COLOUR-NAME>` variables to `--_govuk-button-<COLOUR-NAME>` properties.

To reduce the risk of typos as this is now happening in a few places (button, but also panel and file upload),
introduce a mixin taking care of assigning the correct variables.
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

JavaScript changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
index c6b4affdd..69babbbdd 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -763,7 +763,7 @@ class FileUpload extends ConfigurableComponent {
         const n = document.createElement("span");
         n.className = "govuk-file-upload-button__pseudo-button-container";
         const s = document.createElement("span");
-        s.className = "govuk-button govuk-button--secondary govuk-file-upload-button__pseudo-button", s.innerText = this.i18n.t("chooseFilesButton"), n.appendChild(s), n.insertAdjacentText("beforeend", " ");
+        s.className = "govuk-button govuk-file-upload-button__pseudo-button", s.innerText = this.i18n.t("chooseFilesButton"), n.appendChild(s), n.insertAdjacentText("beforeend", " ");
         const o = document.createElement("span");
         return o.className = "govuk-body govuk-file-upload-button__instruction", o.innerText = this.i18n.t("dropInstruction"), n.appendChild(o), t.appendChild(n), t.setAttribute("aria-labelledby", `${this.$label.id} ${i.id} ${t.id}`), t.addEventListener("click", this.onClick.bind(this)), t.addEventListener("dragover", (t => {
             t.preventDefault()

Action run for ada8799

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Stylesheets changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css
index 3ce5f12e3..0e1170475 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.css
@@ -1668,6 +1668,25 @@
     border-color: currentcolor
 }
 
+:root {
+    --_govuk-button-background-colour: #0f7a52;
+    --_govuk-button-text-colour: #fff;
+    --_govuk-button-hover-background-colour: #0b5c3e;
+    --_govuk-button-shadow-colour: #083d29;
+    --_govuk-secondary-button-background-colour: #f3f3f3;
+    --_govuk-secondary-button-text-colour: #0b0c0c;
+    --_govuk-secondary-button-hover-background-colour: #cecece;
+    --_govuk-secondary-button-shadow-colour: #858686;
+    --_govuk-warning-button-background-colour: #ca3535;
+    --_govuk-warning-button-text-colour: #fff;
+    --_govuk-warning-button-hover-background-colour: #982828;
+    --_govuk-warning-button-shadow-colour: #651b1b;
+    --_govuk-inverse-button-background-colour: #fff;
+    --_govuk-inverse-button-text-colour: var(--govuk-brand-colour, #1d70b8);
+    --_govuk-inverse-button-hover-background-colour: #f4f8fb;
+    --_govuk-inverse-button-shadow-colour: #0f385c
+}
+
 .govuk-button {
     font-family: GDS Transport, arial, sans-serif;
     -webkit-font-smoothing: antialiased;
@@ -1683,9 +1702,9 @@
     padding: 8px 10px 7px;
     border: 2px solid transparent;
     border-radius: 0;
-    color: #fff;
-    background-color: #0f7a52;
-    box-shadow: 0 2px 0 #083d29;
+    color: var(--_govuk-button-text-colour);
+    background-color: var(--_govuk-button-background-colour);
+    box-shadow: 0 2px 0 var(--_govuk-button-shadow-colour);
     text-align: center;
     vertical-align: top;
     cursor: pointer;
@@ -1711,7 +1730,7 @@
 .govuk-button:hover,
 .govuk-button:link,
 .govuk-button:visited {
-    color: #fff;
+    color: var(--_govuk-button-text-colour);
     text-decoration: none
 }
 
@@ -1721,7 +1740,7 @@
 }
 
 .govuk-button:hover {
-    background-color: #0b5c3e
+    background-color: var(--_govuk-button-hover-background-colour)
 }
 
 .govuk-button:active {
@@ -1757,80 +1776,37 @@
 }
 
 .govuk-button[disabled] {
+    --_govuk-button-hover-background-colour: var(--_govuk-button-background-colour);
     opacity: .5
 }
 
 .govuk-button[disabled]:hover {
-    background-color: #0f7a52;
     cursor: not-allowed
 }
 
 .govuk-button[disabled]:active {
-    top: 0;
-    box-shadow: 0 2px 0 #083d29
+    top: 0
 }
 
 .govuk-button--secondary {
-    background-color: #f3f3f3;
-    box-shadow: 0 2px 0 #858686
-}
-
-.govuk-button--secondary,
-.govuk-button--secondary:active,
-.govuk-button--secondary:hover,
-.govuk-button--secondary:link,
-.govuk-button--secondary:visited {
-    color: #0b0c0c
-}
-
-.govuk-button--secondary:hover {
-    background-color: #cecece
-}
-
-.govuk-button--secondary:hover[disabled] {
-    background-color: #f3f3f3
+    --_govuk-button-background-colour: var(--_govuk-secondary-button-background-colour);
+    --_govuk-button-shadow-colour: var(--_govuk-secondary-button-shadow-colour);
+    --_govuk-button-text-colour: var(--_govuk-secondary-button-text-colour);
+    --_govuk-button-hover-background-colour: var(--_govuk-secondary-button-hover-background-colour)
 }
 
 .govuk-button--warning {
-    background-color: #ca3535;
-    box-shadow: 0 2px 0 #651b1b
-}
-
-.govuk-button--warning,
-.govuk-button--warning:active,
-.govuk-button--warning:hover,
-.govuk-button--warning:link,
-.govuk-button--warning:visited {
-    color: #fff
-}
-
-.govuk-button--warning:hover {
-    background-color: #982828
-}
-
-.govuk-button--warning:hover[disabled] {
-    background-color: #ca3535
+    --_govuk-button-background-colour: var(--_govuk-warning-button-background-colour);
+    --_govuk-button-shadow-colour: var(--_govuk-warning-button-shadow-colour);
+    --_govuk-button-text-colour: var(--_govuk-warning-button-text-colour);
+    --_govuk-button-hover-background-colour: var(--_govuk-warning-button-hover-background-colour)
 }
 
 .govuk-button--inverse {
-    background-color: #fff;
-    box-shadow: 0 2px 0 #0f385c
-}
-
-.govuk-button--inverse,
-.govuk-button--inverse:active,
-.govuk-button--inverse:hover,
-.govuk-button--inverse:link,
-.govuk-button--inverse:visited {
-    color: var(--govuk-brand-colour, #1d70b8)
-}
-
-.govuk-button--inverse:hover {
-    background-color: #f4f8fb
-}
-
-.govuk-button--inverse:hover[disabled] {
-    background-color: #fff
+    --_govuk-button-background-colour: var(--_govuk-inverse-button-background-colour);
+    --_govuk-button-shadow-colour: var(--_govuk-inverse-button-shadow-colour);
+    --_govuk-button-text-colour: var(--_govuk-inverse-button-text-colour);
+    --_govuk-button-hover-background-colour: var(--_govuk-inverse-button-hover-background-colour)
 }
 
 .govuk-button--start {
@@ -3134,7 +3110,11 @@ screen and (forced-colors:active) {
     padding: 18px;
     border: 2px solid #cecece;
     background-color: #fff;
-    cursor: pointer
+    cursor: pointer;
+    --_govuk-button-background-colour: var(--_govuk-secondary-button-background-colour);
+    --_govuk-button-shadow-colour: var(--_govuk-secondary-button-shadow-colour);
+    --_govuk-button-text-colour: var(--_govuk-secondary-button-text-colour);
+    --_govuk-button-hover-background-colour: var(--_govuk-secondary-button-hover-background-colour)
 }
 
 @media (min-width:40.0625em) {
@@ -3148,19 +3128,12 @@ screen and (forced-colors:active) {
     border-color: var(--govuk-error-colour, #ca3535)
 }
 
-.govuk-file-upload-button .govuk-file-upload-button__pseudo-button {
-    background-color: #f3f3f3
-}
-
 .govuk-file-upload-button:hover {
+    --_govuk-button-background-colour: var(--_govuk-button-hover-background-colour);
     border-style: dashed;
     border-color: #858686
 }
 
-.govuk-file-upload-button:hover .govuk-file-upload-button__pseudo-button {
-    background-color: #cecece
-}
-
 .govuk-file-upload-button:hover .govuk-file-upload-button__status {
     background-color: #1d70b8
 }
@@ -3172,20 +3145,16 @@ screen and (forced-colors:active) {
     outline-color: var(--govuk-focus-colour, #fd0);
     outline-offset: 0;
     background-color: #f3f3f3;
-    box-shadow: inset 0 0 0 2px
-}
-
-.govuk-file-upload-button:active .govuk-file-upload-button__pseudo-button,
-.govuk-file-upload-button:focus .govuk-file-upload-button__pseudo-button {
-    background-color: var(--govuk-focus-colour, #fd0);
-    box-shadow: 0 2px 0 #0b0c0c
+    box-shadow: inset 0 0 0 2px;
+    --_govuk-button-background-colour: var(--govuk-focus-colour, #fd0);
+    --_govuk-button-shadow-colour: var(--govuk-focus-text-colour, #0b0c0c)
 }
 
 .govuk-file-upload-button:active:hover .govuk-file-upload-button__pseudo-button,
 .govuk-file-upload-button:focus:hover .govuk-file-upload-button__pseudo-button {
+    --_govuk-button-background-colour: var(--_govuk-button-hover-background-colour);
     border-color: var(--govuk-focus-colour, #fd0);
     outline: 3px solid transparent;
-    background-color: #cecece;
     box-shadow: inset 0 0 0 1px var(--govuk-focus-colour, #fd0)
 }
 
@@ -3194,10 +3163,6 @@ screen and (forced-colors:active) {
     background-color: #fff
 }
 
-.govuk-file-upload-button--empty .govuk-file-upload-button__pseudo-button {
-    background-color: #f3f3f3
-}
-
 .govuk-file-upload-button--empty .govuk-file-upload-button__status {
     color: #0b0c0c;
     background-color: #d2e2f1
@@ -3230,10 +3195,6 @@ screen and (forced-colors:active) {
     background-color: #fff
 }
 
-.govuk-file-upload-button:disabled .govuk-file-upload-button__pseudo-button {
-    background-color: #f3f3f3
-}
-
 .govuk-file-upload-button:disabled .govuk-file-upload-button__status {
     background-color: #d2e2f1
 }
@@ -4224,7 +4185,11 @@ screen and (forced-colors:active) {
     padding: 35px;
     border: 5px solid transparent;
     color: var(--govuk-text-colour, #0b0c0c);
-    text-align: center
+    text-align: center;
+    --_govuk-button-background-colour: var(--_govuk-inverse-button-background-colour);
+    --_govuk-button-shadow-colour: var(--_govuk-inverse-button-shadow-colour);
+    --_govuk-button-text-colour: var(--_govuk-inverse-button-text-colour);
+    --_govuk-button-hover-background-colour: var(--_govuk-inverse-button-hover-background-colour)
 }
 
 @media print {

Action run for ada8799

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Rendered HTML changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/components/panel/template-interruption-with-content-with-long-line-length.html b/packages/govuk-frontend/dist/govuk/components/panel/template-interruption-with-content-with-long-line-length.html
index edf984265..8d4c492a9 100644
--- a/packages/govuk-frontend/dist/govuk/components/panel/template-interruption-with-content-with-long-line-length.html
+++ b/packages/govuk-frontend/dist/govuk/components/panel/template-interruption-with-content-with-long-line-length.html
@@ -9,7 +9,7 @@
     <p class="govuk-body">You can go back to undo this change.</p>
   </div>
   <div class="govuk-panel__actions"><div class="govuk-button-group">
-      <button type="submit" class="govuk-button govuk-button--inverse" data-module="govuk-button" form="the-form-id">
+      <button type="submit" class="govuk-button" data-module="govuk-button" form="the-form-id">
         Are you sure you want to change this?
       </button>
       <a class="govuk-link govuk-link--inverse" href="#">Go back to application</a>
diff --git a/packages/govuk-frontend/dist/govuk/components/panel/template-interruption.html b/packages/govuk-frontend/dist/govuk/components/panel/template-interruption.html
index f095ed646..c0abf45ae 100644
--- a/packages/govuk-frontend/dist/govuk/components/panel/template-interruption.html
+++ b/packages/govuk-frontend/dist/govuk/components/panel/template-interruption.html
@@ -6,7 +6,7 @@
     <p class="govuk-body">You entered your age as <strong>109</strong>.</p>
   </div>
   <div class="govuk-panel__actions"><div class="govuk-button-group">
-      <button type="button" class="govuk-button govuk-button--inverse" data-module="govuk-button">
+      <button type="button" class="govuk-button" data-module="govuk-button">
         Yes, this is correct
       </button>
       <a class="govuk-link govuk-link--inverse" href="#">Change my age</a>

Action run for ada8799

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Other changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/_base.import.scss b/packages/govuk-frontend/dist/govuk/_base.import.scss
index 34ae3a716..287e7c739 100644
--- a/packages/govuk-frontend/dist/govuk/_base.import.scss
+++ b/packages/govuk-frontend/dist/govuk/_base.import.scss
@@ -3,4 +3,5 @@
 @import "helpers";
 @import "custom-properties";
 @import "components/button/settings";
+@import "components/button/helpers";
 
diff --git a/packages/govuk-frontend/dist/govuk/_base.scss b/packages/govuk-frontend/dist/govuk/_base.scss
index 8d0b69c35..b538c2b9e 100644
--- a/packages/govuk-frontend/dist/govuk/_base.scss
+++ b/packages/govuk-frontend/dist/govuk/_base.scss
@@ -2,4 +2,5 @@
 @forward "tools";
 @forward "helpers";
 @forward "components/button/settings";
+@forward "components/button/helpers";
 
diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.js b/packages/govuk-frontend/dist/govuk/all.bundle.js
index 1c5cd4a01..0dd63232a 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.js
@@ -1661,7 +1661,7 @@
       const containerSpan = document.createElement('span');
       containerSpan.className = 'govuk-file-upload-button__pseudo-button-container';
       const buttonSpan = document.createElement('span');
-      buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload-button__pseudo-button';
+      buttonSpan.className = 'govuk-button govuk-file-upload-button__pseudo-button';
       buttonSpan.innerText = this.i18n.t('chooseFilesButton');
       containerSpan.appendChild(buttonSpan);
       containerSpan.insertAdjacentText('beforeend', ' ');
diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.mjs b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
index 1222791b7..da4e301ae 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
@@ -1655,7 +1655,7 @@ class FileUpload extends ConfigurableComponent {
     const containerSpan = document.createElement('span');
     containerSpan.className = 'govuk-file-upload-button__pseudo-button-container';
     const buttonSpan = document.createElement('span');
-    buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload-button__pseudo-button';
+    buttonSpan.className = 'govuk-button govuk-file-upload-button__pseudo-button';
     buttonSpan.innerText = this.i18n.t('chooseFilesButton');
     containerSpan.appendChild(buttonSpan);
     containerSpan.insertAdjacentText('beforeend', ' ');
diff --git a/packages/govuk-frontend/dist/govuk/components/button/_helpers.scss b/packages/govuk-frontend/dist/govuk/components/button/_helpers.scss
new file mode 100644
index 000000000..7acad18f3
--- /dev/null
+++ b/packages/govuk-frontend/dist/govuk/components/button/_helpers.scss
@@ -0,0 +1,30 @@
+////
+/// @group components/button
+////
+
+@use "sass:list";
+
+/// List of the button variants available in GOV.UK Frontend
+$_available-variants: (secondary, warning, inverse);
+
+/// Configure colours of the Button component for the given variant
+///
+/// Use:
+/// - to create modifiers of the `.govuk-button` class to apply a specific variant to a single button
+/// - inside a component to update the default style of all buttons with only the `.govuk-button` class
+///
+/// It configures the `--_govuk-button-<COLOUR_NAME>` custom properties that apply colours to the button
+/// with the appropriate `--_govuk-<VARIANT>-button-<COLOUR_NAME>` properties that define colours for a given variant.
+///
+/// @access private
+@mixin govuk-button-style($variant-name) {
+  @if not list.index($_available-variants, $variant-name) {
+    @error "Unknown button variant `#{$variant-name}` (available variants: #{$_available-variants})";
+  }
+
+  --_govuk-button-background-colour: var(--_govuk-#{$variant-name}-button-background-colour);
+  --_govuk-button-shadow-colour: var(--_govuk-#{$variant-name}-button-shadow-colour);
+  --_govuk-button-text-colour: var(--_govuk-#{$variant-name}-button-text-colour);
+  --_govuk-button-hover-background-colour: var(--_govuk-#{$variant-name}-button-hover-background-colour);
+}
+
diff --git a/packages/govuk-frontend/dist/govuk/components/button/_mixin.scss b/packages/govuk-frontend/dist/govuk/components/button/_mixin.scss
index c992115b7..ae79ba42c 100644
--- a/packages/govuk-frontend/dist/govuk/components/button/_mixin.scss
+++ b/packages/govuk-frontend/dist/govuk/components/button/_mixin.scss
@@ -6,34 +6,37 @@
 
 /// @access private
 @mixin styles($background-colour, $text-colour, $inverse-background-colour, $inverse-text-colour) {
-  $govuk-button-colour: $background-colour;
-  $text-colour: $text-colour;
-  $govuk-button-hover-colour: base.govuk-colour("green", $variant: "shade-25");
-  $govuk-button-shadow-colour: base.govuk-colour("green", $variant: "shade-50");
-
-  // Secondary button variables
-  $govuk-secondary-button-colour: base.govuk-colour("black", $variant: "tint-95");
-  $govuk-secondary-button-text-colour: base.govuk-colour("black");
-  $govuk-secondary-button-hover-colour: base.govuk-colour("black", $variant: "tint-80");
-  $govuk-secondary-button-shadow-colour: base.govuk-colour("black", $variant: "tint-50");
-
-  // Warning button variables
-  $govuk-warning-button-colour: base.govuk-colour("red");
-  $govuk-warning-button-text-colour: base.govuk-colour("white");
-  $govuk-warning-button-hover-colour: base.govuk-colour("red", $variant: "shade-25");
-  $govuk-warning-button-shadow-colour: base.govuk-colour("red", $variant: "shade-50");
-
-  // Inverse button variables
-  $govuk-inverse-button-colour: $inverse-background-colour;
-  $inverse-text-colour: $inverse-text-colour;
-  $govuk-inverse-button-hover-colour: base.govuk-colour("blue", $variant: "tint-95");
-  $govuk-inverse-button-shadow-colour: base.govuk-colour("blue", $variant: "shade-50");
-
   // Because the shadow (s0) is visually 'part of' the button, we need to reduce
   // the height of the button to compensate by adjusting its padding (s1) and
   // increase the bottom margin to include it (s2).
   $button-shadow-size: base.$govuk-border-width-form-element;
 
+  :root {
+    // Default button colours
+    --_govuk-button-background-colour: #{$background-colour};
+    --_govuk-button-text-colour: #{$text-colour};
+    --_govuk-button-hover-background-colour: #{base.govuk-colour("green", $variant: "shade-25")};
+    --_govuk-button-shadow-colour: #{base.govuk-colour("green", $variant: "shade-50")};
+
+    // Secondary button colours
+    --_govuk-secondary-button-background-colour: #{base.govuk-colour("black", $variant: "tint-95")};
+    --_govuk-secondary-button-text-colour: #{base.govuk-colour("black")};
+    --_govuk-secondary-button-hover-background-colour: #{base.govuk-colour("black", $variant: "tint-80")};
+    --_govuk-secondary-button-shadow-colour: #{base.govuk-colour("black", $variant: "tint-50")};
+
+    // Warning button colours
+    --_govuk-warning-button-background-colour: #{base.govuk-colour("red")};
+    --_govuk-warning-button-text-colour: #{base.govuk-colour("white")};
+    --_govuk-warning-button-hover-background-colour: #{base.govuk-colour("red", $variant: "shade-25")};
+    --_govuk-warning-button-shadow-colour: #{base.govuk-colour("red", $variant: "shade-50")};
+
+    // Warning button colours
+    --_govuk-inverse-button-background-colour: #{$inverse-background-colour};
+    --_govuk-inverse-button-text-colour: #{$inverse-text-colour};
+    --_govuk-inverse-button-hover-background-colour: #{base.govuk-colour("blue", $variant: "tint-95")};
+    --_govuk-inverse-button-shadow-colour: #{base.govuk-colour("blue", $variant: "shade-50")};
+  }
+
   .govuk-button {
     @include base.govuk-font($size: 19, $line-height: 19px);
 
@@ -49,9 +52,9 @@
       (base.govuk-spacing(2) - base.$govuk-border-width-form-element - ($button-shadow-size * 0.5)); // s1
     border: base.$govuk-border-width-form-element solid transparent;
     border-radius: 0;
-    color: $text-colour;
-    background-color: $govuk-button-colour;
-    box-shadow: 0 $button-shadow-size 0 $govuk-button-shadow-colour; // s0
+    color: var(--_govuk-button-text-colour);
+    background-color: var(--_govuk-button-background-colour);
+    box-shadow: 0 $button-shadow-size 0 var(--_govuk-button-shadow-colour); // s0
     text-align: center;
     vertical-align: top;
     cursor: pointer;
@@ -66,7 +69,7 @@
     &:visited,
     &:active,
     &:hover {
-      color: $text-colour;
+      color: var(--_govuk-button-text-colour);
       text-decoration: none;
     }
 
@@ -77,7 +80,7 @@
     }
 
     &:hover {
-      background-color: $govuk-button-hover-colour;
+      background-color: var(--_govuk-button-hover-background-colour);
     }
 
     &:active {
@@ -130,80 +133,29 @@
   }
 
   .govuk-button[disabled] {
+    --_govuk-button-hover-background-colour: var(--_govuk-button-background-colour);
+
     opacity: (0.5);
 
     &:hover {
-      background-color: $govuk-button-colour;
       cursor: not-allowed;
     }
 
     &:active {
       top: 0;
-      box-shadow: 0 $button-shadow-size 0 $govuk-button-shadow-colour; // s0
     }
   }
 
   .govuk-button--secondary {
-    background-color: $govuk-secondary-button-colour;
-    box-shadow: 0 $button-shadow-size 0 $govuk-secondary-button-shadow-colour;
-
-    &,
-    &:link,
-    &:visited,
-    &:active,
-    &:hover {
-      color: $govuk-secondary-button-text-colour;
-    }
-
-    &:hover {
-      background-color: $govuk-secondary-button-hover-colour;
-
-      &[disabled] {
-        background-color: $govuk-secondary-button-colour;
-      }
-    }
+    @include base.govuk-button-style(secondary);
   }
 
   .govuk-button--warning {
-    background-color: $govuk-warning-button-colour;
-    box-shadow: 0 $button-shadow-size 0 $govuk-warning-button-shadow-colour;
-
-    &,
-    &:link,
-    &:visited,
-    &:active,
-    &:hover {
-      color: $govuk-warning-button-text-colour;
-    }
-
-    &:hover {
-      background-color: $govuk-warning-button-hover-colour;
-
-      &[disabled] {
-        background-color: $govuk-warning-button-colour;
-      }
-    }
+    @include base.govuk-button-style(warning);
   }
 
   .govuk-button--inverse {
-    background-color: $govuk-inverse-button-colour;
-    box-shadow: 0 $button-shadow-size 0 $govuk-inverse-button-shadow-colour;
-
-    &,
-    &:link,
-    &:visited,
-    &:active,
-    &:hover {
-      color: $inverse-text-colour;
-    }
-
-    &:hover {
-      background-color: $govuk-inverse-button-hover-colour;
-
-      &[disabled] {
-        background-color: $govuk-inverse-button-colour;
-      }
-    }
+    @include base.govuk-button-style(inverse);
   }
 
   .govuk-button--start {
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/_mixin.scss b/packages/govuk-frontend/dist/govuk/components/file-upload/_mixin.scss
index 47a215bef..375bc665f 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/_mixin.scss
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/_mixin.scss
@@ -118,18 +118,14 @@
       border-color: base.govuk-functional-colour("error");
     }
 
-    .govuk-file-upload-button__pseudo-button {
-      background-color: base.govuk-colour("black", $variant: "tint-95");
-    }
+    @include base.govuk-button-style(secondary);
 
     &:hover {
+      --_govuk-button-background-colour: var(--_govuk-button-hover-background-colour);
+
       border-style: dashed;
       border-color: base.govuk-colour("black", $variant: "tint-50");
 
-      .govuk-file-upload-button__pseudo-button {
-        background-color: base.govuk-colour("black", $variant: "tint-80");
-      }
-
       .govuk-file-upload-button__status {
         background-color: base.govuk-colour("blue");
       }
@@ -150,15 +146,13 @@
       // here as it is already used for the yellow focus state.
       box-shadow: inset 0 0 0 base.$govuk-border-width-form-element;
 
-      .govuk-file-upload-button__pseudo-button {
-        background-color: base.govuk-functional-colour(focus);
-        box-shadow: 0 2px 0 base.govuk-colour("black");
-      }
+      --_govuk-button-background-colour: #{base.govuk-functional-colour(focus)};
+      --_govuk-button-shadow-colour: #{base.govuk-functional-colour(focus-text)};
 
       &:hover .govuk-file-upload-button__pseudo-button {
+        --_govuk-button-background-colour: var(--_govuk-button-hover-background-colour);
         border-color: base.govuk-functional-colour(focus);
         outline: 3px solid transparent;
-        background-color: base.govuk-colour("black", $variant: "tint-80");
         box-shadow: inset 0 0 0 1px base.govuk-functional-colour(focus);
       }
     }
@@ -168,10 +162,6 @@
     border-style: dashed;
     background-color: $empty-button-background-colour;
 
-    .govuk-file-upload-button__pseudo-button {
-      background-color: $empty-pseudo-button-background-colour;
-    }
-
     .govuk-file-upload-button__status {
       color: base.govuk-colour("black");
       background-color: $empty-status-background-colour;
@@ -205,10 +195,6 @@
 
     background-color: $empty-button-background-colour;
 
-    .govuk-file-upload-button__pseudo-button {
-      background-color: $empty-pseudo-button-background-colour;
-    }
-
     .govuk-file-upload-button__status {
       background-color: $empty-status-background-colour;
     }
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
index 27e308312..52a1d16d5 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
@@ -454,7 +454,7 @@
       const containerSpan = document.createElement('span');
       containerSpan.className = 'govuk-file-upload-button__pseudo-button-container';
       const buttonSpan = document.createElement('span');
-      buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload-button__pseudo-button';
+      buttonSpan.className = 'govuk-button govuk-file-upload-button__pseudo-button';
       buttonSpan.innerText = this.i18n.t('chooseFilesButton');
       containerSpan.appendChild(buttonSpan);
       containerSpan.insertAdjacentText('beforeend', ' ');
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
index 71147deb2..95f92b750 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
@@ -448,7 +448,7 @@ class FileUpload extends ConfigurableComponent {
     const containerSpan = document.createElement('span');
     containerSpan.className = 'govuk-file-upload-button__pseudo-button-container';
     const buttonSpan = document.createElement('span');
-    buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload-button__pseudo-button';
+    buttonSpan.className = 'govuk-button govuk-file-upload-button__pseudo-button';
     buttonSpan.innerText = this.i18n.t('chooseFilesButton');
     containerSpan.appendChild(buttonSpan);
     containerSpan.insertAdjacentText('beforeend', ' ');
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
index ec60ba0e4..54562c059 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
@@ -92,7 +92,7 @@ class FileUpload extends ConfigurableComponent {
     const containerSpan = document.createElement('span');
     containerSpan.className = 'govuk-file-upload-button__pseudo-button-container';
     const buttonSpan = document.createElement('span');
-    buttonSpan.className = 'govuk-button govuk-button--secondary govuk-file-upload-button__pseudo-button';
+    buttonSpan.className = 'govuk-button govuk-file-upload-button__pseudo-button';
     buttonSpan.innerText = this.i18n.t('chooseFilesButton');
     containerSpan.appendChild(buttonSpan);
     containerSpan.insertAdjacentText('beforeend', ' ');
diff --git a/packages/govuk-frontend/dist/govuk/components/panel/_mixin.scss b/packages/govuk-frontend/dist/govuk/components/panel/_mixin.scss
index 13bc5a122..d8a1bc97b 100644
--- a/packages/govuk-frontend/dist/govuk/components/panel/_mixin.scss
+++ b/packages/govuk-frontend/dist/govuk/components/panel/_mixin.scss
@@ -15,6 +15,8 @@
 
     text-align: center;
 
+    @include base.govuk-button-style(inverse);
+
     @media #{base.govuk-until-breakpoint(tablet)} {
       padding: base.govuk-spacing(4) - base.$govuk-border-width;
 
diff --git a/packages/govuk-frontend/dist/govuk/components/panel/fixtures.json b/packages/govuk-frontend/dist/govuk/components/panel/fixtures.json
index 186ca9cab..81599bbde 100644
--- a/packages/govuk-frontend/dist/govuk/components/panel/fixtures.json
+++ b/packages/govuk-frontend/dist/govuk/components/panel/fixtures.json
@@ -36,7 +36,7 @@
             "description": "",
             "pageTemplateOptions": {},
             "screenshot": false,
-            "html": "<div class=\"govuk-panel govuk-panel--interruption\">\n  <h1 class=\"govuk-panel__title\">\n    Is your age correct?\n  </h1>\n  <div class=\"govuk-panel__body\">\n    <p class=\"govuk-body\">You entered your age as <strong>109</strong>.</p>\n  </div>\n  <div class=\"govuk-panel__actions\"><div class=\"govuk-button-group\">\n      <button type=\"button\" class=\"govuk-button govuk-button--inverse\" data-module=\"govuk-button\">\n        Yes, this is correct\n      </button>\n      <a class=\"govuk-link govuk-link--inverse\" href=\"#\">Change my age</a>\n    </div></div>\n</div>"
+            "html": "<div class=\"govuk-panel govuk-panel--interruption\">\n  <h1 class=\"govuk-panel__title\">\n    Is your age correct?\n  </h1>\n  <div class=\"govuk-panel__body\">\n    <p class=\"govuk-body\">You entered your age as <strong>109</strong>.</p>\n  </div>\n  <div class=\"govuk-panel__actions\"><div class=\"govuk-button-group\">\n      <button type=\"button\" class=\"govuk-button\" data-module=\"govuk-button\">\n        Yes, this is correct\n      </button>\n      <a class=\"govuk-link govuk-link--inverse\" href=\"#\">Change my age</a>\n    </div></div>\n</div>"
         },
         {
             "name": "interruption-with-content-with-long-line-length",
@@ -64,7 +64,7 @@
             "description": "",
             "pageTemplateOptions": {},
             "screenshot": false,
-            "html": "<div class=\"govuk-panel govuk-panel--interruption\">\n  <h1 class=\"govuk-panel__title\">\n    Are you sure you want to change this?\n  </h1>\n  <div class=\"govuk-panel__body\">\n    <p class=\"govuk-body\">You've changed the person's address from Wales to England. This means that the referral needs to be cancelled.</p>\n    <p class=\"govuk-body\">Previous home address: 1 Willow Lane, Newchurch, Kington, HR5 3QF</p>\n    <p class=\"govuk-body\">New home address: 9 Elm Street, Whitney-on-Wye, Hereford, HR3 6EH</p>\n    <p class=\"govuk-body\">You can go back to undo this change.</p>\n  </div>\n  <div class=\"govuk-panel__actions\"><div class=\"govuk-button-group\">\n      <button type=\"submit\" class=\"govuk-button govuk-button--inverse\" data-module=\"govuk-button\" form=\"the-form-id\">\n        Are you sure you want to change this?\n      </button>\n      <a class=\"govuk-link govuk-link--inverse\" href=\"#\">Go back to application</a>\n    </div></div>\n</div>"
+            "html": "<div class=\"govuk-panel govuk-panel--interruption\">\n  <h1 class=\"govuk-panel__title\">\n    Are you sure you want to change this?\n  </h1>\n  <div class=\"govuk-panel__body\">\n    <p class=\"govuk-body\">You've changed the person's address from Wales to England. This means that the referral needs to be cancelled.</p>\n    <p class=\"govuk-body\">Previous home address: 1 Willow Lane, Newchurch, Kington, HR5 3QF</p>\n    <p class=\"govuk-body\">New home address: 9 Elm Street, Whitney-on-Wye, Hereford, HR3 6EH</p>\n    <p class=\"govuk-body\">You can go back to undo this change.</p>\n  </div>\n  <div class=\"govuk-panel__actions\"><div class=\"govuk-button-group\">\n      <button type=\"submit\" class=\"govuk-button\" data-module=\"govuk-button\" form=\"the-form-id\">\n        Are you sure you want to change this?\n      </button>\n      <a class=\"govuk-link govuk-link--inverse\" href=\"#\">Go back to application</a>\n    </div></div>\n</div>"
         },
         {
             "name": "interruption-with-headings-content-and-lists",
@@ -254,7 +254,7 @@
             "description": "",
             "pageTemplateOptions": {},
             "screenshot": false,
-            "html": "<div class=\"govuk-panel govuk-panel--interruption\">\n  <h1 class=\"govuk-panel__title\">\n    Is your age correct?\n  </h1>\n  <div class=\"govuk-panel__body\">\n    <p class=\"govuk-body\">You entered your age as <strong>109</strong>.</p>\n  </div>\n  <div class=\"govuk-panel__actions extra-class one-more-class\" data-test=\"1\" data-other-test=\"yes\"><div class=\"govuk-button-group\">\n      <button type=\"button\" class=\"govuk-button govuk-button--inverse\" data-module=\"govuk-button\">\n        Yes, this is correct\n      </button>\n      <a class=\"govuk-link govuk-link--inverse\" href=\"#\">Change my age</a>\n    </div></div>\n</div>"
+            "html": "<div class=\"govuk-panel govuk-panel--interruption\">\n  <h1 class=\"govuk-panel__title\">\n    Is your age correct?\n  </h1>\n  <div class=\"govuk-panel__body\">\n    <p class=\"govuk-body\">You entered your age as <strong>109</strong>.</p>\n  </div>\n  <div class=\"govuk-panel__actions extra-class one-more-class\" data-test=\"1\" data-other-test=\"yes\"><div class=\"govuk-button-group\">\n      <button type=\"button\" class=\"govuk-button\" data-module=\"govuk-button\">\n        Yes, this is correct\n      </button>\n      <a class=\"govuk-link govuk-link--inverse\" href=\"#\">Change my age</a>\n    </div></div>\n</div>"
         },
         {
             "name": "interruption, submit action",
@@ -285,7 +285,7 @@
             "description": "",
             "pageTemplateOptions": {},
             "screenshot": false,
-            "html": "<div class=\"govuk-panel govuk-panel--interruption\">\n  <h1 class=\"govuk-panel__title\">\n    Is your age correct?\n  </h1>\n  <div class=\"govuk-panel__body\">\n    <p class=\"govuk-body\">You entered your age as <strong>109</strong>.</p>\n  </div>\n  <div class=\"govuk-panel__actions\"><div class=\"govuk-button-group\">\n      <button type=\"submit\" class=\"govuk-button govuk-button--inverse extra-class one-more-class\" data-module=\"govuk-button\" form=\"test-target-element\" name=\"acknowledged\" value=\"\">\n        Yes, this is correct\n      </button>\n      <a class=\"govuk-link govuk-link--inverse\" href=\"#\">Change my age</a>\n    </div></div>\n</div>"
+            "html": "<div class=\"govuk-panel govuk-panel--interruption\">\n  <h1 class=\"govuk-panel__title\">\n    Is your age correct?\n  </h1>\n  <div class=\"govuk-panel__body\">\n    <p class=\"govuk-body\">You entered your age as <strong>109</strong>.</p>\n  </div>\n  <div class=\"govuk-panel__actions\"><div class=\"govuk-button-group\">\n      <button type=\"submit\" class=\"govuk-button extra-class one-more-class\" data-module=\"govuk-button\" form=\"test-target-element\" name=\"acknowledged\" value=\"\">\n        Yes, this is correct\n      </button>\n      <a class=\"govuk-link govuk-link--inverse\" href=\"#\">Change my age</a>\n    </div></div>\n</div>"
         },
         {
             "name": "interruption, button link",
@@ -316,7 +316,7 @@
             "description": "",
             "pageTemplateOptions": {},
             "screenshot": false,
-            "html": "<div class=\"govuk-panel govuk-panel--interruption\">\n  <h1 class=\"govuk-panel__title\">\n    Is your age correct?\n  </h1>\n  <div class=\"govuk-panel__body\">\n    <p class=\"govuk-body\">You entered your age as <strong>109</strong>.</p>\n  </div>\n  <div class=\"govuk-panel__actions extra-class one-more-class\" data-test=\"1\" data-other-test=\"yes\"><div class=\"govuk-button-group\">\n      <a href=\"?acknowledged=true\" role=\"button\" draggable=\"false\" class=\"govuk-button govuk-button--inverse\" data-module=\"govuk-button\">\n        Yes, this is correct\n      </a>\n      <a class=\"govuk-link govuk-link--inverse\" href=\"#\">Change my age</a>\n    </div></div>\n</div>"
+            "html": "<div class=\"govuk-panel govuk-panel--interruption\">\n  <h1 class=\"govuk-panel__title\">\n    Is your age correct?\n  </h1>\n  <div class=\"govuk-panel__body\">\n    <p class=\"govuk-body\">You entered your age as <strong>109</strong>.</p>\n  </div>\n  <div class=\"govuk-panel__actions extra-class one-more-class\" data-test=\"1\" data-other-test=\"yes\"><div class=\"govuk-button-group\">\n      <a href=\"?acknowledged=true\" role=\"button\" draggable=\"false\" class=\"govuk-button\" data-module=\"govuk-button\">\n        Yes, this is correct\n      </a>\n      <a class=\"govuk-link govuk-link--inverse\" href=\"#\">Change my age</a>\n    </div></div>\n</div>"
         },
         {
             "name": "interruption, no actions",

Action run for ada8799

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

📋 Stats

File sizes

File Size Percentage change
dist/govuk-frontend-development.min.css 124.07 KiB 1%
dist/govuk-frontend-development.min.js 45.93 KiB -0.1%
packages/govuk-frontend/dist/govuk/all.bundle.js 99.32 KiB -0%
packages/govuk-frontend/dist/govuk/all.bundle.mjs 93.33 KiB -0%
packages/govuk-frontend/dist/govuk/govuk-frontend.min.css 124.06 KiB 1%
packages/govuk-frontend/dist/govuk/govuk-frontend.min.js 45.91 KiB -0.1%

Modules

File Size (bundled) Percentage change (bundled) Size (minified) Percentage change (bundled)
all.mjs 87.3 KiB -0% 43.65 KiB -0.1%
file-upload.mjs 18.87 KiB -0.1% 10.09 KiB -0.2%

View stats and visualisations on the review app


Action run for ada8799


background-color: $empty-button-background-colour;

.govuk-file-upload-button__pseudo-button {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: I need to review this component with the different states and check still looks as expected since it's complex

"text": action.text,
"type": action.type if action.type else "button",
"classes": "govuk-button--inverse" + (" " + action.classes if action.classes),
"classes": action.classes if action.classes,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants