diff --git a/src/components/checkbox/checkbox.styles.ts b/src/components/checkbox/checkbox.styles.ts
index db09e7cfd1..8aaa724fcd 100644
--- a/src/components/checkbox/checkbox.styles.ts
+++ b/src/components/checkbox/checkbox.styles.ts
@@ -60,9 +60,19 @@ export default css`
.checkbox__checked-icon,
.checkbox__indeterminate-icon {
- display: inline-flex;
+ display: flex;
width: var(--toggle-size);
height: var(--toggle-size);
+ align-items: center;
+ justify-content: center;
+ }
+
+ .checkbox__icon-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
}
/* Hover */
diff --git a/src/components/checkbox/checkbox.test.ts b/src/components/checkbox/checkbox.test.ts
index 0a05a65bfc..d9b7a7b736 100644
--- a/src/components/checkbox/checkbox.test.ts
+++ b/src/components/checkbox/checkbox.test.ts
@@ -352,18 +352,45 @@ describe('
', () => {
describe('indeterminate', () => {
it('should render indeterminate icon until checked', async () => {
const el = await fixture(html``);
- let indeterminateIcon = el.shadowRoot!.querySelector('[part~="indeterminate-icon"]')!;
+ const container = el.shadowRoot!.querySelector('[part="indeterminate-icon-container"]');
- expect(indeterminateIcon).not.to.be.null;
+ expect(container).not.to.be.null;
+ expect(container!.hasAttribute('hidden')).to.be.false;
el.click();
await el.updateComplete;
- indeterminateIcon = el.shadowRoot!.querySelector('[part~="indeterminate-icon"]')!;
-
- expect(indeterminateIcon).to.be.null;
+ expect(container!.hasAttribute('hidden')).to.be.true;
});
runFormControlBaseTests('sl-checkbox');
});
+
+ describe('custom icons', () => {
+ it('should allow custom checked icon via slot', async () => {
+ const el = await fixture(html`
+
+ ✓
+
+ `);
+ const slot = el.shadowRoot!.querySelector('slot[name="checked-icon"]')!;
+ const assignedElements = (slot as HTMLSlotElement).assignedElements() as Element[];
+
+ expect(assignedElements.length).to.equal(1);
+ expect(assignedElements[0].textContent).to.equal('✓');
+ });
+
+ it('should allow custom indeterminate icon via slot', async () => {
+ const el = await fixture(html`
+
+ -
+
+ `);
+ const slot = el.shadowRoot!.querySelector('slot[name="indeterminate-icon"]')!;
+ const assignedElements = (slot as HTMLSlotElement).assignedElements() as Element[];
+
+ expect(assignedElements.length).to.equal(1);
+ expect(assignedElements[0].textContent).to.equal('-');
+ });
+ });
});
diff --git a/src/components/option/option.component.ts b/src/components/option/option.component.ts
index f3950cfeda..34cc508c42 100644
--- a/src/components/option/option.component.ts
+++ b/src/components/option/option.component.ts
@@ -20,9 +20,12 @@ import type { CSSResultGroup } from 'lit';
* @slot - The option's label.
* @slot prefix - Used to prepend an icon or similar element to the menu item.
* @slot suffix - Used to append an icon or similar element to the menu item.
+ * @slot checked-icon - The icon to show when the option is selected.
*
- * @csspart checked-icon - The checked icon, an `` element.
* @csspart base - The component's base wrapper.
+ * @csspart checked-icon - The checked icon.
+ * @csspart checked-icon-container - The container for the checked icon.
+ * @csspart empty-icon - The placeholder icon space when not selected.
* @csspart label - The option's label.
* @csspart prefix - The container that wraps the prefix.
* @csspart suffix - The container that wraps the suffix.
@@ -138,7 +141,14 @@ export default class SlOption extends ShoelaceElement {
@mouseenter=${this.handleMouseEnter}
@mouseleave=${this.handleMouseLeave}
>
-
+
diff --git a/src/components/option/option.styles.ts b/src/components/option/option.styles.ts
index 146c7cbf1a..350f0810a7 100644
--- a/src/components/option/option.styles.ts
+++ b/src/components/option/option.styles.ts
@@ -44,6 +44,20 @@ export default css`
cursor: not-allowed;
}
+ .option__icon-container {
+ display: flex;
+ width: 20px;
+ margin-inline-end: var(--sl-spacing-2x-small);
+ flex-shrink: 0;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .option__empty-icon {
+ width: 20px;
+ height: 20px;
+ }
+
.option__label {
flex: 1 1 auto;
display: inline-block;
@@ -55,12 +69,8 @@ export default css`
display: flex;
align-items: center;
justify-content: center;
- visibility: hidden;
- padding-inline-end: var(--sl-spacing-2x-small);
- }
-
- .option--selected .option__check {
- visibility: visible;
+ width: 100%;
+ height: 100%;
}
.option__prefix,
diff --git a/src/components/option/option.test.ts b/src/components/option/option.test.ts
index ac7f243a8a..1d3bbe8ff0 100644
--- a/src/components/option/option.test.ts
+++ b/src/components/option/option.test.ts
@@ -45,4 +45,25 @@ describe('', () => {
const el = await fixture(html` Option `);
expect(el.getTextLabel()).to.equal('Option');
});
+
+ it('should render a custom check icon when provided via slot', async () => {
+ const el = await fixture(html`
+
+ ✓
+ Option 1
+
+ `);
+
+ await el.updateComplete;
+
+ const iconContainer = el.shadowRoot!.querySelector('.option__icon-container')!;
+ expect(iconContainer).to.be.visible;
+
+ const slotElement = iconContainer.querySelector('slot[name="checked-icon"]');
+ expect(slotElement).to.be.visible;
+
+ const customIcon = el.querySelector('div[slot="checked-icon"]');
+ expect(customIcon).to.be.visible;
+ expect(customIcon!.textContent).to.equal('✓');
+ });
});