Skip to content

Conversation

@bjarnef
Copy link
Contributor

@bjarnef bjarnef commented Jan 3, 2026

Description

I noticed we have a uui-radio-group component, but no uui-checkbox-group component as most other webcomponent libraries I have came across:
https://react-spectrum.adobe.com/v3/CheckboxGroup.html
https://storybook.heroui.com/?path=/story/components-checkboxgroup--default
https://www.radix-ui.com/themes/docs/components/checkbox-group
https://base-ui.com/react/components/checkbox-group

This can be useful for various thing, including form validation of checkbox group, but also make it a bit more consistent with radios.

The checkbox inherits from the boolean input (which toggle also inherits from), but not sure if it would be better checkbox and radio component inherits from a base component instead as these share more similaries.

Default value prorperty need to be an array instead of string as this example:
https://www.radix-ui.com/themes/docs/components/checkbox-group

I think it may be better to move uui-radio-group to it's own folder as this is how much other components are structured and it allows to have separated docs & stories.

The keyboard constants in radio group place should probably be moved to a central place, so other components can use these as well.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Chore (minor updates related to the tooling or maintenance of the repository, does not impact compiled assets)

Motivation and context

More consistent with radios and allow us to handle configuration on group itself, e.g. an orientation property with vertical | horizontal options - default vertical.

Easier to control gap/spacing between checkbox options (and not just personal preferences when listing checkboxes).

I have started using most similar logic as in radio group component, but some of it can be removed as it doesn't need to consider single choice as checkbox group will always be a multiple selection.

Other things could be relevant like mark as required, validation state, error message etc.
A label/description per checkbox/radio group may also be useful and from accessibility point of view.

How to test?

Screenshots (if appropriate)

image

Checklist

  • If my change requires a change to the documentation, I have updated the documentation in this pull request.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.

Copilot AI review requested due to automatic review settings January 3, 2026 14:19
@github-actions
Copy link

github-actions bot commented Jan 3, 2026

Hi there @bjarnef, thank you for this contribution! 👍

While we wait for the team to have a look at your work, we wanted to let you know about that we have a checklist for some of the things we will consider during review:

  • It's clear what problem this is solving, there's a connected issue or a description of what the changes do and how to test them
  • The automated tests all pass (see "Checks" tab on this PR)
  • The level of security for this contribution is the same or improved
  • The level of performance for this contribution is the same or improved
  • Avoids creating breaking changes; note that behavioral changes might also be perceived as breaking
  • If this is a new feature, Umbraco HQ provided guidance on the implementation beforehand
  • 💡 The contribution looks original and the contributor is presumably allowed to share it

Don't worry if you got something wrong. We like to think of a pull request as the start of a conversation, we're happy to provide guidance on improving your contribution.

If you realize that you might want to make some changes then you can do that by adding new commits to the branch you created for this work and pushing new commits. They should then automatically show up as updates to this pull request.

Thanks, from your friendly Umbraco GitHub bot 🤖 🙂

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request introduces a new uui-checkbox-group component to provide consistency with the existing uui-radio-group component and enable form validation for groups of checkboxes.

Key Changes:

  • New uui-checkbox-group component with supporting event classes
  • Additional methods added to uui-checkbox element (check(), uncheck(), makeFocusable(), makeUnfocusable())
  • Package configuration, tests, stories, and documentation for the new component

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
packages/uui/lib/index.ts Exports the new checkbox-group component
packages/uui-radio/lib/uui-radio.element.ts Minor formatting changes (added blank lines)
packages/uui-checkbox/lib/uui-checkbox.element.ts Adds group-related methods (check, uncheck, focus management)
packages/uui-checkbox/lib/index.ts Exports UUICheckboxEvent class
packages/uui-checkbox/lib/UUICheckboxEvent.ts New event class for checkbox change events
packages/uui-checkbox-group/tsconfig.json TypeScript configuration for the new package
packages/uui-checkbox-group/rollup.config.js Build configuration for the new package
packages/uui-checkbox-group/package.json Package metadata and dependencies
packages/uui-checkbox-group/lib/uui-checkbox-group.test.ts Basic tests for the component (minimal coverage)
packages/uui-checkbox-group/lib/uui-checkbox-group.story.ts Storybook examples demonstrating usage
packages/uui-checkbox-group/lib/uui-checkbox-group.element.ts Main component implementation
packages/uui-checkbox-group/lib/index.ts Package exports
packages/uui-checkbox-group/lib/UUICheckboxGroupEvent.ts Event class for checkbox group changes
packages/uui-checkbox-group/README.md Component documentation
package-lock.json Dependency lock file updates

Critical Issues Found:

The implementation has a fundamental flaw: it implements single-selection behavior (like radio buttons) when checkbox groups should support multiple simultaneous selections. The component needs significant rework to properly handle multiple checked values, correct keyboard navigation, and appropriate focus management for checkbox semantics rather than radio button semantics.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +158 to +163
#onSelectClick = (e: UUICheckboxEvent) => {
if (e.target.checked === true) {
this.value = e.target.value;
this.#fireChangeEvent();
}
};
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The checkbox group currently implements single-selection behavior (similar to radio buttons), but checkbox groups should support multiple selections. The #onSelectClick method only updates the value when a checkbox is checked to true, and the #updateCheckboxElementsCheckedState method unchecks all other checkboxes when one is selected. This contradicts the fundamental purpose of checkboxes, which is to allow multiple items to be selected simultaneously. The value property should be an array of selected values, not a single string.

Copilot uses AI. Check for mistakes.
Comment on lines 239 to 257
#updateCheckboxElementsCheckedState(newValue: FormData | FormDataEntryValue) {
const notChecked: Array<UUICheckboxElement> = [];

this.#checkboxElements.forEach((el, index) => {
if (el.value === newValue) {
el.checked = true;
el.makeFocusable();
this.#selected = index;
} else {
el.checked = false;
notChecked.push(el);
}
});

// If there is a selected checkbox, make all other checkboxes unfocusable.
if (this.#selected !== null) {
notChecked.forEach(el => el.makeUnfocusable());
}
}
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

This method treats checkboxes as mutually exclusive (only one can be checked at a time), which is incorrect for checkbox groups. When a checkbox value matches newValue, all other checkboxes are unchecked. This should instead support multiple checked values by accepting an array or delimited string of values and checking all matching checkboxes.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +18
import { html, fixture, expect } from '@open-wc/testing';
import { UUICheckboxGroupElement } from './uui-checkbox-group.element';

describe('UUICheckboxGroupElement', () => {
let element: UUICheckboxGroupElement;

beforeEach(async () => {
element = await fixture(html` <uui-checkbox-group></uui-checkbox-group> `);
});

it('is defined with its own instance', () => {
expect(element).to.be.instanceOf(UUICheckboxGroupElement);
});

it('passes the a11y audit', async () => {
await expect(element).shadowDom.to.be.accessible();
});
});
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The test coverage for the checkbox group is minimal. There are no tests for multiple selection behavior, value handling, keyboard navigation, or interaction between multiple checkboxes. The radio group component has comprehensive tests that should be adapted for checkbox group behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +93
export const DefaultValue: Story = {
render: () => html`
<uui-checkbox-group value="3">
<uui-checkbox value="1">one</uui-checkbox>
<uui-checkbox value="2">two</uui-checkbox>
<uui-checkbox value="3">three</uui-checkbox>
<uui-checkbox value="4">four</uui-checkbox>
</uui-checkbox-group>
`,
};
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The DefaultValue story sets value="3" on the checkbox group, which with the current single-selection implementation would only check one checkbox. For a proper checkbox group implementation that supports multiple selections, this should demonstrate setting multiple default values, for example value="1,3" or using an array format.

Copilot uses AI. Check for mistakes.
}

/**
* Call to make the element focusable, this sets tabindex to -1.
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The documentation comment incorrectly states that makeUnfocusable sets tabindex to -1 when it should say "make the element unfocusable". The method name already indicates this action.

Suggested change
* Call to make the element focusable, this sets tabindex to -1.
* Call to make the element unfocusable.

Copilot uses AI. Check for mistakes.
Comment on lines 291 to 301
#selectPreviousCheckbox() {
this.value = this.#findAdjacentCheckboxElement(-1)?.value ?? '';
this.#checkboxElements[this.#selected ?? 0]?.focus();
this.#fireChangeEvent();
}

#selectNextCheckbox() {
this.value = this.#findAdjacentCheckboxElement()?.value ?? '';
this.#checkboxElements[this.#selected ?? 0]?.focus();
this.#fireChangeEvent();
}
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The keyboard navigation with arrow keys implements single-selection behavior by selecting the previous checkbox and deselecting others. For checkbox groups, arrow keys should typically move focus without changing selection state. Space or Enter should toggle the focused checkbox's checked state while preserving other selections.

Copilot uses AI. Check for mistakes.
this.#updateCheckboxElementsCheckedState(newValue as string);
}

#selected: number | null = null;
Copy link

Copilot AI Jan 3, 2026

Choose a reason for hiding this comment

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

The #selected field tracks a single selected index, which is appropriate for radio groups but not for checkbox groups where multiple checkboxes can be selected simultaneously. This should either be removed or changed to track an array of selected indices if needed for focus management.

Copilot uses AI. Check for mistakes.
@bjarnef
Copy link
Contributor Author

bjarnef commented Jan 3, 2026

@iOvergaard @nielslyngsoe it seems there is a small inconsistency between uui-radio and uui-checkbox.

For uui-radio this render label:

<uui-radio
        value="Prolific - I live and breathe Umbraco."
        label="Prolific - I live and breathe Umbraco.">
</uui-radio>

For uui-checkbox this doesn't render label as the slotted content probably not is seen as empty, but a whitespace:

<uui-checkbox
        value="Prolific - I live and breathe Umbraco."
        label="Prolific - I live and breathe Umbraco.">
</uui-checkbox>

but this does:

<uui-checkbox
        value="Prolific - I live and breathe Umbraco."
        label="Prolific - I live and breathe Umbraco."></uui-checkbox>
image

fe528b0 accounts for only whitespace in slotted content as uui-radio, so both examples above works + if label is set via property:

image

bjarnef and others added 6 commits January 3, 2026 18:22
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 4, 2026

import { css, html } from 'lit';

//import { UUICheckboxEvent } from './UUICheckboxEvent';

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure about this. Ideally checkbox emit UUICheckboxEvent as radio emit UUIRadioEvent, but checkbox inherits boolean input. Can we override change event inherited, but I guess it may possible be a breaking change - or perhaps trigger both events?

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.

1 participant