diff --git a/.changeset/tiny-clubs-behave.md b/.changeset/tiny-clubs-behave.md
new file mode 100644
index 000000000..99a697a33
--- /dev/null
+++ b/.changeset/tiny-clubs-behave.md
@@ -0,0 +1,5 @@
+---
+'@ithaka/pharos': minor
+---
+
+Add full-width attribute to radio and checkbox components, which makes them fill their parent container. Default radio-group and checkbox-group width to 100%
diff --git a/packages/pharos/src/components/checkbox-group/pharos-checkbox-group.scss b/packages/pharos/src/components/checkbox-group/pharos-checkbox-group.scss
index 1448572ba..9e3fe8fcc 100644
--- a/packages/pharos/src/components/checkbox-group/pharos-checkbox-group.scss
+++ b/packages/pharos/src/components/checkbox-group/pharos-checkbox-group.scss
@@ -6,6 +6,7 @@
.checkbox-group {
border: 0;
padding: 0;
+ width: 100%;
}
.checkbox-group__checkboxes {
diff --git a/packages/pharos/src/components/checkbox/PharosCheckbox.react.stories.tsx b/packages/pharos/src/components/checkbox/PharosCheckbox.react.stories.tsx
index 66bb8cca2..f8df2551d 100644
--- a/packages/pharos/src/components/checkbox/PharosCheckbox.react.stories.tsx
+++ b/packages/pharos/src/components/checkbox/PharosCheckbox.react.stories.tsx
@@ -1,6 +1,6 @@
import { action } from 'storybook/actions';
-import { PharosCheckbox, PharosLink } from '../../react-components';
+import { PharosCheckbox, PharosCheckboxGroup, PharosLink } from '../../react-components';
import { defaultArgs, type ComponentArgs, type StoryArgs } from './storyArgs';
import { configureDocsPage } from '../../utils/_storybook/docsPageConfig';
import { PharosContext } from '../../utils/PharosContext';
@@ -122,6 +122,65 @@ export const Validity: Story = {
},
};
+export const FullWidth: Story = {
+ render: () => (
+ <>
+
+
+
+ Full Width
+
+ This is the first choice
+
+
+ This is the second choice
+
+
+
+ This is the third choice with a label that is just entirely too long and someone
+ should have said something before shipping this to users
+
+
+
+
+ Full Width with styles
+
+ This is the first choice
+
+
+ This is the second choice
+
+
+
+ This is the third choice with a label that is just entirely too long and someone
+ should have said something before shipping this to users
+
+
+
+
+ >
+ ),
+};
+
export const IsOnBackground: Story = {
name: 'On background',
render: () => (
diff --git a/packages/pharos/src/components/checkbox/pharos-checkbox.scss b/packages/pharos/src/components/checkbox/pharos-checkbox.scss
index d8bda8487..ddd9dd1ca 100644
--- a/packages/pharos/src/components/checkbox/pharos-checkbox.scss
+++ b/packages/pharos/src/components/checkbox/pharos-checkbox.scss
@@ -10,6 +10,15 @@
@include mixins.option-wrapper;
}
+:host([full-width]) {
+ width: 100%;
+}
+
+// Grow the label to fill the row so the entire width is clickable, not just the text
+:host([full-width]) label {
+ flex: 1;
+}
+
.input__icon {
display: block;
cursor: pointer;
diff --git a/packages/pharos/src/components/checkbox/pharos-checkbox.test.ts b/packages/pharos/src/components/checkbox/pharos-checkbox.test.ts
index 11d169984..fecadf7a6 100644
--- a/packages/pharos/src/components/checkbox/pharos-checkbox.test.ts
+++ b/packages/pharos/src/components/checkbox/pharos-checkbox.test.ts
@@ -284,6 +284,28 @@ describe('pharos-checkbox', () => {
expect(clickSpy.callCount).to.equal(1);
});
+ it('stretches to fill its container when full-width is set', async () => {
+ const parentNode = document.createElement('div');
+ parentNode.style.width = '400px';
+ component = await fixture(
+ html`test checkbox`,
+ { parentNode }
+ );
+ expect(getComputedStyle(component).width).to.equal('400px');
+ });
+
+ it('does not stretch to fill its container by default', async () => {
+ const parentNode = document.createElement('div');
+ parentNode.style.width = '400px';
+ component = await fixture(
+ html`test checkbox`,
+ { parentNode }
+ );
+ expect(getComputedStyle(component).width).to.not.equal('400px');
+ });
+
it('resets checked when the form is reset', async () => {
const parentNode = document.createElement('form');
parentNode.setAttribute('name', 'my-form');
diff --git a/packages/pharos/src/components/checkbox/pharos-checkbox.ts b/packages/pharos/src/components/checkbox/pharos-checkbox.ts
index 4f00e0673..b7672d055 100644
--- a/packages/pharos/src/components/checkbox/pharos-checkbox.ts
+++ b/packages/pharos/src/components/checkbox/pharos-checkbox.ts
@@ -52,6 +52,13 @@ export class PharosCheckbox extends FormMixin(FormElement) {
@property({ type: Boolean, reflect: true })
public isOnBackground = false;
+ /**
+ * Stretches the checkbox to fill the width of its container
+ * @attr full-width
+ */
+ @property({ type: Boolean, reflect: true })
+ public fullWidth = false;
+
@query('#checkbox-element')
private _checkbox!: HTMLInputElement;
diff --git a/packages/pharos/src/components/checkbox/pharos-checkbox.wc.stories.ts b/packages/pharos/src/components/checkbox/pharos-checkbox.wc.stories.ts
index 62ecefbff..4699c1c11 100644
--- a/packages/pharos/src/components/checkbox/pharos-checkbox.wc.stories.ts
+++ b/packages/pharos/src/components/checkbox/pharos-checkbox.wc.stories.ts
@@ -109,6 +109,57 @@ export const Validity: Story = {
},
};
+export const FullWidth: Story = {
+ render: () =>
+ html`
+
+
+ Full Width
+ This is the first choice
+ This is the second choice
+ This is the third choice with a label that is just entirely too long and someone
+ should have said something before shipping this to users
+
+
+ Full Width with styles
+ This is the first choice
+ This is the second choice
+ This is the third choice with a label that is just entirely too long and someone
+ should have said something before shipping this to users
+
+
`,
+};
+
export const IsOnBackground: Story = {
name: 'On background',
render: () => html`
diff --git a/packages/pharos/src/components/radio-button/PharosRadioButton.react.stories.tsx b/packages/pharos/src/components/radio-button/PharosRadioButton.react.stories.tsx
index 0344dcc98..c92789121 100644
--- a/packages/pharos/src/components/radio-button/PharosRadioButton.react.stories.tsx
+++ b/packages/pharos/src/components/radio-button/PharosRadioButton.react.stories.tsx
@@ -1,7 +1,7 @@
import { Fragment } from 'react';
import { action } from 'storybook/actions';
-import { PharosRadioButton, PharosLink } from '../../react-components';
+import { PharosRadioButton, PharosRadioGroup, PharosLink } from '../../react-components';
import { configureDocsPage } from '../../utils/_storybook/docsPageConfig';
import { defaultArgs, type ComponentArgs, type StoryArgs } from './storyArgs';
import { PharosContext } from '../../utils/PharosContext';
@@ -123,3 +123,67 @@ export const Validity: Story = {
message: 'This field is required, please make a selection',
},
};
+
+export const FullWidth: Story = {
+ render: () => (
+ <>
+
+
+
+ Full Width
+
+ This is the first choice
+
+
+ This is the second choice
+
+
+
+ This is the third choice with a label that is just entirely too long and someone
+ should have said something before shipping this to users
+
+
+
+
+ Full Width with styles
+
+ This is the first choice
+
+
+ This is the second choice
+
+
+
+ This is the third choice with a label that is just entirely too long and someone
+ should have said something before shipping this to users
+
+
+
+
+ >
+ ),
+};
diff --git a/packages/pharos/src/components/radio-button/pharos-radio-button.scss b/packages/pharos/src/components/radio-button/pharos-radio-button.scss
index 012c978b8..efb20c2d8 100644
--- a/packages/pharos/src/components/radio-button/pharos-radio-button.scss
+++ b/packages/pharos/src/components/radio-button/pharos-radio-button.scss
@@ -10,6 +10,15 @@
@include mixins.option-wrapper;
}
+:host([full-width]) {
+ width: 100%;
+}
+
+// Grow the label to fill the row so the entire width is clickable, not just the text
+:host([full-width]) label {
+ flex: 1;
+}
+
.input__icon {
display: block;
cursor: pointer;
diff --git a/packages/pharos/src/components/radio-button/pharos-radio-button.test.ts b/packages/pharos/src/components/radio-button/pharos-radio-button.test.ts
index a3c7a6faf..9ad048485 100644
--- a/packages/pharos/src/components/radio-button/pharos-radio-button.test.ts
+++ b/packages/pharos/src/components/radio-button/pharos-radio-button.test.ts
@@ -239,6 +239,30 @@ describe('pharos-radio-button', () => {
expect(count).to.equal(1);
});
+ it('stretches to fill its container when full-width is set', async () => {
+ const parentNode = document.createElement('div');
+ parentNode.style.width = '400px';
+ component = await fixture(
+ html`test radio`,
+ { parentNode }
+ );
+ expect(getComputedStyle(component).width).to.equal('400px');
+ });
+
+ it('does not stretch to fill its container by default', async () => {
+ const parentNode = document.createElement('div');
+ parentNode.style.width = '400px';
+ component = await fixture(
+ html`test radio`,
+ { parentNode }
+ );
+ expect(getComputedStyle(component).width).to.not.equal('400px');
+ });
+
it('resets checked when the form is reset', async () => {
const parentNode = document.createElement('form');
parentNode.setAttribute('name', 'my-form');
diff --git a/packages/pharos/src/components/radio-button/pharos-radio-button.ts b/packages/pharos/src/components/radio-button/pharos-radio-button.ts
index 8fe8c7150..25787b8dd 100644
--- a/packages/pharos/src/components/radio-button/pharos-radio-button.ts
+++ b/packages/pharos/src/components/radio-button/pharos-radio-button.ts
@@ -33,6 +33,13 @@ export class PharosRadioButton extends FormMixin(FormElement) {
@property({ type: String, reflect: true })
public value = '';
+ /**
+ * Stretches the radio button to fill the width of its container
+ * @attr full-width
+ */
+ @property({ type: Boolean, reflect: true })
+ public fullWidth = false;
+
@query('#radio-element')
private _radio!: HTMLInputElement;
diff --git a/packages/pharos/src/components/radio-button/pharos-radio-button.wc.stories.ts b/packages/pharos/src/components/radio-button/pharos-radio-button.wc.stories.ts
index 112293e75..cfcb00c5d 100644
--- a/packages/pharos/src/components/radio-button/pharos-radio-button.wc.stories.ts
+++ b/packages/pharos/src/components/radio-button/pharos-radio-button.wc.stories.ts
@@ -102,3 +102,59 @@ export const Validity: Story = {
message: 'This field is required, please make a selection',
},
};
+
+export const FullWidth: Story = {
+ render: () =>
+ html`
+
+
+ Full Width
+ This is the first choice
+ This is the second choice
+ This is the third choice with a label that is just entirely too long and someone
+ should have said something before shipping this to users
+
+
+ Full Width with styles
+ This is the first choice
+ This is the second choice
+ This is the third choice with a label that is just entirely too long and someone
+ should have said something before shipping this to users
+
+
`,
+};
diff --git a/packages/pharos/src/components/radio-group/pharos-radio-group.scss b/packages/pharos/src/components/radio-group/pharos-radio-group.scss
index 3d1080ae9..5f2fe8857 100644
--- a/packages/pharos/src/components/radio-group/pharos-radio-group.scss
+++ b/packages/pharos/src/components/radio-group/pharos-radio-group.scss
@@ -6,6 +6,7 @@
.radio-group {
border: 0;
padding: 0;
+ width: 100%;
}
.radio-group__radios {