diff --git a/cspell.json b/cspell.json
index 1ae873361..028a62671 100644
--- a/cspell.json
+++ b/cspell.json
@@ -160,6 +160,7 @@
"unbundles",
"unbundling",
"unicons",
+ "unsanitized",
"unsupportive",
"valpha",
"valuenow",
diff --git a/docs/pages/components/select.md b/docs/pages/components/select.md
index 6fd05cbe1..4705dc07b 100644
--- a/docs/pages/components/select.md
+++ b/docs/pages/components/select.md
@@ -454,3 +454,53 @@ const App = () => (
>
);
```
+
+### Custom Tags
+
+When multiple options can be selected, you can provide custom tags by passing a function to the `getTag` property. Your function can return a string of HTML, a Lit Template, or an [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement). The `getTag()` function will be called for each option. The first argument is an `` element and the second argument is the tag's index (its position in the tag list).
+
+Remember that custom tags are rendered in a shadow root. To style them, you can use the `style` attribute in your template or you can add your own [parts](/getting-started/customizing/#css-parts) and target them with the [`::part()`](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) selector.
+
+```html:preview
+
+
+
+ Email
+
+
+
+ Phone
+
+
+
+ Chat
+
+
+
+
+```
+
+:::warning
+Be sure you trust the content you are outputting! Passing unsanitized user input to `getTag()` can result in XSS vulnerabilities.
+:::
diff --git a/src/components/select/select.component.ts b/src/components/select/select.component.ts
index 54d809854..9bc5a0cd3 100644
--- a/src/components/select/select.component.ts
+++ b/src/components/select/select.component.ts
@@ -8,6 +8,7 @@ import { html } from 'lit';
import { LocalizeController } from '../../utilities/localize.js';
import { property, query, state } from 'lit/decorators.js';
import { scrollIntoView } from '../../internal/scroll.js';
+import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { waitForEvent } from '../../internal/event.js';
import { watch } from '../../internal/watch.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
@@ -15,7 +16,7 @@ import SlIcon from '../icon/icon.component.js';
import SlPopup from '../popup/popup.component.js';
import SlTag from '../tag/tag.component.js';
import styles from './select.styles.js';
-import type { CSSResultGroup } from 'lit';
+import type { CSSResultGroup, TemplateResult } from 'lit';
import type { ShoelaceFormControl } from '../../internal/shoelace-element.js';
import type SlOption from '../option/option.component.js';
import type SlRemoveEvent from '../../events/sl-remove.js';
@@ -172,6 +173,31 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
/** The select's required attribute. */
@property({ type: Boolean, reflect: true }) required = false;
+ /**
+ * A function that customizes the tags to be rendered when multiple=true. The first argument is the option, the second
+ * is the current tag's index. The function should return either a Lit TemplateResult or a string containing trusted HTML of the symbol to render at
+ * the specified value.
+ */
+ @property() getTag: (option: SlOption, index: number) => TemplateResult | string | HTMLElement = option => {
+ return html`
+ this.handleTagRemove(event, option)}
+ >
+ ${option.getTextLabel()}
+
+ `;
+ };
+
/** Gets the validity state object */
get validity() {
return this.valueInput.validity;
@@ -547,6 +573,21 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
this.formControlController.updateValidity();
});
}
+ protected get tags() {
+ return this.selectedOptions.map((option, index) => {
+ if (index < this.maxOptionsVisible || this.maxOptionsVisible <= 0) {
+ const tag = this.getTag(option, index);
+ // Wrap so we can handle the remove
+ return html`