diff --git a/custom-elements-manifest.config.js b/custom-elements-manifest.config.js
index cb8c3ae4ec..2af8eade7b 100644
--- a/custom-elements-manifest.config.js
+++ b/custom-elements-manifest.config.js
@@ -2,6 +2,7 @@ import * as path from 'path';
import { customElementJetBrainsPlugin } from 'custom-element-jet-brains-integration';
import { customElementVsCodePlugin } from 'custom-element-vs-code-integration';
import { customElementVuejsPlugin } from 'custom-element-vuejs-integration';
+import { jsxTypesPlugin } from '@wc-toolkit/jsx-types';
import { parse } from 'comment-parser';
import { pascalCase } from 'pascal-case';
import commandLineArgs from 'command-line-args';
@@ -225,6 +226,14 @@ export default {
outdir: './dist/types/vue',
fileName: 'index.d.ts',
componentTypePath: (_, tag) => `../../components/${tag.replace('sl-', '')}/${tag.replace('sl-', '')}.component.js`
+ }),
+ jsxTypesPlugin({
+ outdir: './dist/types/jsx',
+ fileName: 'index.d.ts',
+ allowUnknownProps: true,
+ defaultExport: true,
+ componentTypePath: (_, tag) =>
+ `../../components/${tag?.replace('sl-', '')}/${tag?.replace('sl-', '')}.component.js`
})
]
};
diff --git a/docs/_includes/sidebar.njk b/docs/_includes/sidebar.njk
index df0958bb48..5e53dcf88e 100644
--- a/docs/_includes/sidebar.njk
+++ b/docs/_includes/sidebar.njk
@@ -18,6 +18,7 @@
Vue
Angular
Svelte
+ JSX
diff --git a/docs/pages/frameworks/jsx.md b/docs/pages/frameworks/jsx.md
new file mode 100644
index 0000000000..955a0b8917
--- /dev/null
+++ b/docs/pages/frameworks/jsx.md
@@ -0,0 +1,155 @@
+---
+meta:
+ title: JSX
+ description: Tips for using Shoelace in JSX.
+---
+
+# JSX Integration
+
+Shoelace provides comprehensive JSX/TSX support with full TypeScript definitions. This makes it easy to use Shoelace components in any JSX-based framework like React (19+), Preact, or Solid.js.
+
+## Installation
+
+First, install Shoelace:
+
+```bash
+npm install @shoelace-style/shoelace
+```
+
+## TypeScript Setup
+
+In order for teams to take advantage of this, all they need to do is import the types in their project. There are two ways to configure the JSX types:
+
+### Add Types to Config
+
+Add the types to your tsconfig.json:
+
+```json
+{
+ "compilerOptions": {
+ "types": ["@shoelace-style/shoelace/dist/types/jsx"]
+ }
+}
+```
+
+### Manual Type Extension
+
+Alternatively, you can manually extend the JSX namespace in your own type definition file:
+
+```tsx
+// types/jsx.d.ts
+import type { CustomElements } from '@shoelace-style/shoelace/dist/types/jsx';
+
+// The module name is typically something like 'react', 'preact'
+// or whatever the package name is that provides JSX support.
+declare module 'react' {
+ namespace JSX {
+ interface IntrinsicElements extends CustomElements {}
+ }
+}
+```
+
+## Basic Usage
+
+Import the components you need and use them like any other JSX element:
+
+```tsx
+import '@shoelace-style/shoelace/dist/themes/light.css';
+import '@shoelace-style/shoelace/dist/components/button/button.js';
+import '@shoelace-style/shoelace/dist/components/card/card.js';
+import '@shoelace-style/shoelace/dist/components/icon/icon.js';
+
+function App() {
+ const handleClick = () => {
+ console.log('Button clicked!');
+ };
+
+ return (
+
+ Welcome to Shoelace
+ This is a Shoelace card component used in JSX.
+
+
+ Star
+
+
+ );
+}
+```
+
+## Event Handling
+
+Shoelace components emit custom events. The JSX types provide properly typed event handlers:
+
+```tsx
+import '@shoelace-style/shoelace/dist/components/input/input.js';
+import '@shoelace-style/shoelace/dist/components/select/select.js';
+
+function FormComponent() {
+ const handleInput = (event: Event) => {
+ const input = event.target as HTMLInputElement;
+ console.log('Input value:', input.value);
+ };
+
+ const handleChange = (event: CustomEvent) => {
+ console.log('Selection changed:', event.detail.item.value);
+ };
+
+ return (
+
+
+
+
+ Option 1
+ Option 2
+ Option 3
+
+
+ );
+}
+```
+
+## Slots
+
+Use the `slot` attribute to place content in named slots:
+
+```tsx
+import '@shoelace-style/shoelace/dist/components/dialog/dialog.js';
+import '@shoelace-style/shoelace/dist/components/button/button.js';
+
+function DialogExample() {
+ return (
+
+ This content goes in the default slot.
+
+
+ Cancel
+
+
+ Confirm
+
+
+ );
+}
+```
+
+## Type Safety
+
+The JSX types provide full IntelliSense support and type checking:
+
+```tsx
+// ✅ Valid - all properties are properly typed
+
+
+// ❌ Invalid - TypeScript will catch these errors
+
+```
diff --git a/docs/pages/frameworks/react.md b/docs/pages/frameworks/react.md
index 65f374842c..570cf45d69 100644
--- a/docs/pages/frameworks/react.md
+++ b/docs/pages/frameworks/react.md
@@ -8,6 +8,10 @@ meta:
Shoelace offers a React version of every component to provide an idiomatic experience for React users. You can easily toggle between HTML and React examples throughout the documentation.
+:::tip
+If you are using React 19+, you may want to try out our [JSX types](/frameworks/jsx/) to use the web components directly in your React components.
+:::
+
## Installation
To add Shoelace to your React app, install the package from npm.
diff --git a/package-lock.json b/package-lock.json
index e0a215c784..5b31a4118f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,6 +26,7 @@
"@types/react": "^18.3.13",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
+ "@wc-toolkit/jsx-types": "^1.2.2",
"@web/dev-server-esbuild": "^1.0.3",
"@web/test-runner": "^0.19.0",
"@web/test-runner-commands": "^0.9.0",
@@ -2746,6 +2747,13 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
+ "node_modules/@wc-toolkit/jsx-types": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@wc-toolkit/jsx-types/-/jsx-types-1.2.2.tgz",
+ "integrity": "sha512-o7h7Ku1B8khvKyH8zBWT8B0uaz3STUt9x1STrt/0/bgw8zZe4/Wn3b76Qe1VIbTg094UDdFc4C4xFRgvu2yyUQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@web/browser-logs": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@web/browser-logs/-/browser-logs-0.4.0.tgz",
@@ -17532,6 +17540,12 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
+ "@wc-toolkit/jsx-types": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@wc-toolkit/jsx-types/-/jsx-types-1.2.2.tgz",
+ "integrity": "sha512-o7h7Ku1B8khvKyH8zBWT8B0uaz3STUt9x1STrt/0/bgw8zZe4/Wn3b76Qe1VIbTg094UDdFc4C4xFRgvu2yyUQ==",
+ "dev": true
+ },
"@web/browser-logs": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@web/browser-logs/-/browser-logs-0.4.0.tgz",
diff --git a/package.json b/package.json
index 47f26e7937..4d3339c4e3 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"./dist/shoelace-autoloader.js": "./dist/shoelace-autoloader.js",
"./dist/themes": "./dist/themes",
"./dist/themes/*": "./dist/themes/*",
+ "./dist/types/*": "./dist/types/*",
"./dist/components": "./dist/components",
"./dist/components/*": "./dist/components/*",
"./dist/utilities": "./dist/utilities",
@@ -87,6 +88,7 @@
"@types/react": "^18.3.13",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
+ "@wc-toolkit/jsx-types": "^1.2.2",
"@web/dev-server-esbuild": "^1.0.3",
"@web/test-runner": "^0.19.0",
"@web/test-runner-commands": "^0.9.0",
diff --git a/src/components/alert/alert.component.ts b/src/components/alert/alert.component.ts
index 7b442cd6ff..a47170aa70 100644
--- a/src/components/alert/alert.component.ts
+++ b/src/components/alert/alert.component.ts
@@ -60,8 +60,10 @@ export default class SlAlert extends ShoelaceElement {
return this.currentToastStack;
}
+ /** @internal */
@query('[part~="base"]') base: HTMLElement;
+ /** @internal */
@query('.alert__countdown-elapsed') countdownElement: HTMLElement;
/**
diff --git a/src/components/animated-image/animated-image.component.ts b/src/components/animated-image/animated-image.component.ts
index c5a5747403..a34b5f6e17 100644
--- a/src/components/animated-image/animated-image.component.ts
+++ b/src/components/animated-image/animated-image.component.ts
@@ -30,9 +30,13 @@ export default class SlAnimatedImage extends ShoelaceElement {
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = { 'sl-icon': SlIcon };
+ /** @internal */
@query('.animated-image__animated') animatedImage: HTMLImageElement;
+ /** @internal */
@state() frozenFrame: string;
+
+ /** @internal */
@state() isLoaded = false;
/** The path to the image to load. */
diff --git a/src/components/animation/animation.component.ts b/src/components/animation/animation.component.ts
index 3a0d3257b8..ab004db87d 100644
--- a/src/components/animation/animation.component.ts
+++ b/src/components/animation/animation.component.ts
@@ -26,6 +26,7 @@ export default class SlAnimation extends ShoelaceElement {
private animation?: Animation;
private hasStarted = false;
+ /** @internal */
@queryAsync('slot') defaultSlot: Promise;
/** The name of the built-in animation to use. For custom animations, use the `keyframes` prop. */
diff --git a/src/components/breadcrumb-item/breadcrumb-item.component.ts b/src/components/breadcrumb-item/breadcrumb-item.component.ts
index 2afbac1d94..682d41abfb 100644
--- a/src/components/breadcrumb-item/breadcrumb-item.component.ts
+++ b/src/components/breadcrumb-item/breadcrumb-item.component.ts
@@ -32,6 +32,7 @@ export default class SlBreadcrumbItem extends ShoelaceElement {
private readonly hasSlotController = new HasSlotController(this, 'prefix', 'suffix');
+ /** @internal */
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
@state() private renderType: 'button' | 'link' | 'dropdown' = 'button';
diff --git a/src/components/breadcrumb/breadcrumb.component.ts b/src/components/breadcrumb/breadcrumb.component.ts
index 4f31493557..5952110071 100644
--- a/src/components/breadcrumb/breadcrumb.component.ts
+++ b/src/components/breadcrumb/breadcrumb.component.ts
@@ -28,7 +28,10 @@ export default class SlBreadcrumb extends ShoelaceElement {
private readonly localize = new LocalizeController(this);
private separatorDir = this.localize.dir();
+ /** @internal */
@query('slot') defaultSlot: HTMLSlotElement;
+
+ /** @internal */
@query('slot[name="separator"]') separatorSlot: HTMLSlotElement;
/**
diff --git a/src/components/button-group/button-group.component.ts b/src/components/button-group/button-group.component.ts
index a842bdf979..7b47a10315 100644
--- a/src/components/button-group/button-group.component.ts
+++ b/src/components/button-group/button-group.component.ts
@@ -18,8 +18,10 @@ import type { CSSResultGroup } from 'lit';
export default class SlButtonGroup extends ShoelaceElement {
static styles: CSSResultGroup = [componentStyles, styles];
+ /** @internal */
@query('slot') defaultSlot: HTMLSlotElement;
+ /** @internal */
@state() disableRole = false;
/**
diff --git a/src/components/button/button.component.ts b/src/components/button/button.component.ts
index f0bd228f62..3f68e84804 100644
--- a/src/components/button/button.component.ts
+++ b/src/components/button/button.component.ts
@@ -52,10 +52,15 @@ export default class SlButton extends ShoelaceElement implements ShoelaceFormCon
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');
private readonly localize = new LocalizeController(this);
+ /** @internal */
@query('.button') button: HTMLButtonElement | HTMLLinkElement;
@state() private hasFocus = false;
+
+ /** @internal */
@state() invalid = false;
+
+ /** @internal */
@property() title = ''; // make reactive to pass through
/** The button's theme variant. */
diff --git a/src/components/carousel/carousel.component.ts b/src/components/carousel/carousel.component.ts
index dd0c3e4978..401116b084 100644
--- a/src/components/carousel/carousel.component.ts
+++ b/src/components/carousel/carousel.component.ts
@@ -81,14 +81,19 @@ export default class SlCarousel extends ShoelaceElement {
/** When set, it is possible to scroll through the slides by dragging them with the mouse. */
@property({ type: Boolean, reflect: true, attribute: 'mouse-dragging' }) mouseDragging = false;
+ /** @internal */
@query('.carousel__slides') scrollContainer: HTMLElement;
+
+ /** @internal */
@query('.carousel__pagination') paginationContainer: HTMLElement;
- // The index of the active slide
+ /** @internal The index of the active slide */
@state() activeSlide = 0;
+ /** @internal */
@state() scrolling = false;
+ /** @internal */
@state() dragging = false;
private autoplayController = new AutoplayController(this, () => this.next());
diff --git a/src/components/checkbox/checkbox.component.ts b/src/components/checkbox/checkbox.component.ts
index 603dd4e655..30fadf4933 100644
--- a/src/components/checkbox/checkbox.component.ts
+++ b/src/components/checkbox/checkbox.component.ts
@@ -52,10 +52,12 @@ export default class SlCheckbox extends ShoelaceElement implements ShoelaceFormC
});
private readonly hasSlotController = new HasSlotController(this, 'help-text');
+ /** @internal */
@query('input[type="checkbox"]') input: HTMLInputElement;
@state() private hasFocus = false;
+ /** @internal */
@property() title = ''; // make reactive to pass through
/** The name of the checkbox, submitted as a name/value pair with form data. */
diff --git a/src/components/color-picker/color-picker.component.ts b/src/components/color-picker/color-picker.component.ts
index b9fb6889ab..a002e5b1f3 100644
--- a/src/components/color-picker/color-picker.component.ts
+++ b/src/components/color-picker/color-picker.component.ts
@@ -106,10 +106,19 @@ export default class SlColorPicker extends ShoelaceElement implements ShoelaceFo
private isSafeValue = false;
private readonly localize = new LocalizeController(this);
+ /** @internal */
@query('[part~="base"]') base: HTMLElement;
+
+ /** @internal */
@query('[part~="input"]') input: SlInput;
+
+ /** @internal */
@query('.color-dropdown') dropdown: SlDropdown;
+
+ /** @internal */
@query('[part~="preview"]') previewButton: HTMLButtonElement;
+
+ /** @internal */
@query('[part~="trigger"]') trigger: HTMLButtonElement;
@state() private hasFocus = false;
diff --git a/src/components/copy-button/copy-button.component.ts b/src/components/copy-button/copy-button.component.ts
index cdcbbd80de..b1354f6a14 100644
--- a/src/components/copy-button/copy-button.component.ts
+++ b/src/components/copy-button/copy-button.component.ts
@@ -50,12 +50,22 @@ export default class SlCopyButton extends ShoelaceElement {
private readonly localize = new LocalizeController(this);
+ /** @internal */
@query('slot[name="copy-icon"]') copyIcon: HTMLSlotElement;
+
+ /** @internal */
@query('slot[name="success-icon"]') successIcon: HTMLSlotElement;
+
+ /** @internal */
@query('slot[name="error-icon"]') errorIcon: HTMLSlotElement;
+
+ /** @internal */
@query('sl-tooltip') tooltip: SlTooltip;
+ /** @internal */
@state() isCopying = false;
+
+ /** @internal */
@state() status: 'rest' | 'success' | 'error' = 'rest';
/** The text value to copy. */
diff --git a/src/components/details/details.component.ts b/src/components/details/details.component.ts
index a1c4b55cd5..62f497913c 100644
--- a/src/components/details/details.component.ts
+++ b/src/components/details/details.component.ts
@@ -48,11 +48,19 @@ export default class SlDetails extends ShoelaceElement {
private readonly localize = new LocalizeController(this);
+ /** @internal */
@query('.details') details: HTMLDetailsElement;
+
+ /** @internal */
@query('.details__header') header: HTMLElement;
+
+ /** @internal */
@query('.details__body') body: HTMLElement;
+
+ /** @internal */
@query('.details__expand-icon-slot') expandIconSlot: HTMLSlotElement;
+ /** @internal */
detailsObserver: MutationObserver;
/**
@@ -127,6 +135,7 @@ export default class SlDetails extends ShoelaceElement {
}
}
+ /** @internal */
@watch('open', { waitUntilFirstUpdate: true })
async handleOpenChange() {
if (this.open) {
diff --git a/src/components/dialog/dialog.component.ts b/src/components/dialog/dialog.component.ts
index 724b9c0b4f..5ad1dbcb7c 100644
--- a/src/components/dialog/dialog.component.ts
+++ b/src/components/dialog/dialog.component.ts
@@ -76,11 +76,18 @@ export default class SlDialog extends ShoelaceElement {
private readonly hasSlotController = new HasSlotController(this, 'footer');
private readonly localize = new LocalizeController(this);
private originalTrigger: HTMLElement | null;
- public modal = new Modal(this);
private closeWatcher: CloseWatcher | null;
+ /** @internal */
+ public modal = new Modal(this);
+
+ /** @internal */
@query('.dialog') dialog: HTMLElement;
+
+ /** @internal */
@query('.dialog__panel') panel: HTMLElement;
+
+ /** @internal */
@query('.dialog__overlay') overlay: HTMLElement;
/**
@@ -155,6 +162,7 @@ export default class SlDialog extends ShoelaceElement {
}
};
+ /** @internal */
@watch('open', { waitUntilFirstUpdate: true })
async handleOpenChange() {
if (this.open) {
@@ -333,6 +341,7 @@ setDefaultAnimation('dialog.show', {
options: { duration: 250, easing: 'ease' }
});
+/** @internal */
setDefaultAnimation('dialog.hide', {
keyframes: [
{ opacity: 1, scale: 1 },
diff --git a/src/components/drawer/drawer.component.ts b/src/components/drawer/drawer.component.ts
index a1ce0c5200..9970d93e72 100644
--- a/src/components/drawer/drawer.component.ts
+++ b/src/components/drawer/drawer.component.ts
@@ -82,11 +82,18 @@ export default class SlDrawer extends ShoelaceElement {
private readonly hasSlotController = new HasSlotController(this, 'footer');
private readonly localize = new LocalizeController(this);
private originalTrigger: HTMLElement | null;
- public modal = new Modal(this);
private closeWatcher: CloseWatcher | null;
+ /** @internal */
+ public modal = new Modal(this);
+
+ /** @internal */
@query('.drawer') drawer: HTMLElement;
+
+ /** @internal */
@query('.drawer__panel') panel: HTMLElement;
+
+ /** @internal */
@query('.drawer__overlay') overlay: HTMLElement;
/**
@@ -179,6 +186,7 @@ export default class SlDrawer extends ShoelaceElement {
}
};
+ /** @internal */
@watch('open', { waitUntilFirstUpdate: true })
async handleOpenChange() {
if (this.open) {
@@ -281,6 +289,7 @@ export default class SlDrawer extends ShoelaceElement {
}
}
+ /** @internal */
@watch('contained', { waitUntilFirstUpdate: true })
handleNoModalChange() {
if (this.open && !this.contained) {
diff --git a/src/components/dropdown/dropdown.component.ts b/src/components/dropdown/dropdown.component.ts
index 4dea944c4d..350ff250ba 100644
--- a/src/components/dropdown/dropdown.component.ts
+++ b/src/components/dropdown/dropdown.component.ts
@@ -47,8 +47,13 @@ export default class SlDropdown extends ShoelaceElement {
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = { 'sl-popup': SlPopup };
+ /** @internal */
@query('.dropdown') popup: SlPopup;
+
+ /** @internal */
@query('.dropdown__trigger') trigger: HTMLSlotElement;
+
+ /** @internal */
@query('.dropdown__panel') panel: HTMLSlotElement;
private readonly localize = new LocalizeController(this);
@@ -229,7 +234,7 @@ export default class SlDropdown extends ShoelaceElement {
}
};
- handleTriggerClick() {
+ private handleTriggerClick() {
if (this.open) {
this.hide();
} else {
@@ -238,7 +243,7 @@ export default class SlDropdown extends ShoelaceElement {
}
}
- async handleTriggerKeyDown(event: KeyboardEvent) {
+ private async handleTriggerKeyDown(event: KeyboardEvent) {
// When spacebar/enter is pressed, show the panel but don't focus on the menu. This let's the user press the same
// key again to hide the menu in case they don't want to make a selection.
if ([' ', 'Enter'].includes(event.key)) {
@@ -286,14 +291,14 @@ export default class SlDropdown extends ShoelaceElement {
}
}
- handleTriggerKeyUp(event: KeyboardEvent) {
+ private handleTriggerKeyUp(event: KeyboardEvent) {
// Prevent space from triggering a click event in Firefox
if (event.key === ' ') {
event.preventDefault();
}
}
- handleTriggerSlotChange() {
+ private handleTriggerSlotChange() {
this.updateAccessibleTrigger();
}
@@ -307,7 +312,7 @@ export default class SlDropdown extends ShoelaceElement {
//
// To determine this, we assume the first tabbable element in the trigger slot is the "accessible trigger."
//
- updateAccessibleTrigger() {
+ private updateAccessibleTrigger() {
const assignedElements = this.trigger.assignedElements({ flatten: true }) as HTMLElement[];
const accessibleTrigger = assignedElements.find(el => getTabbableBoundary(el).start);
let target: HTMLElement;
@@ -357,7 +362,7 @@ export default class SlDropdown extends ShoelaceElement {
this.popup.reposition();
}
- addOpenListeners() {
+ private addOpenListeners() {
this.panel.addEventListener('sl-select', this.handlePanelSelect);
if ('CloseWatcher' in window) {
this.closeWatcher?.destroy();
@@ -373,7 +378,7 @@ export default class SlDropdown extends ShoelaceElement {
document.addEventListener('mousedown', this.handleDocumentMouseDown);
}
- removeOpenListeners() {
+ private removeOpenListeners() {
if (this.panel) {
this.panel.removeEventListener('sl-select', this.handlePanelSelect);
this.panel.removeEventListener('keydown', this.handleKeyDown);
@@ -383,6 +388,7 @@ export default class SlDropdown extends ShoelaceElement {
this.closeWatcher?.destroy();
}
+ /** @internal */
@watch('open', { waitUntilFirstUpdate: true })
async handleOpenChange() {
if (this.disabled) {
diff --git a/src/components/icon-button/icon-button.component.ts b/src/components/icon-button/icon-button.component.ts
index 587f19a3f1..05f8d69074 100644
--- a/src/components/icon-button/icon-button.component.ts
+++ b/src/components/icon-button/icon-button.component.ts
@@ -25,6 +25,7 @@ export default class SlIconButton extends ShoelaceElement {
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = { 'sl-icon': SlIcon };
+ /** @internal */
@query('.icon-button') button: HTMLButtonElement | HTMLLinkElement;
@state() private hasFocus = false;
@@ -76,17 +77,17 @@ export default class SlIconButton extends ShoelaceElement {
}
}
- /** Simulates a click on the icon button. */
+ /** @internal Simulates a click on the icon button. */
click() {
this.button.click();
}
- /** Sets focus on the icon button. */
+ /** @internal Sets focus on the icon button. */
focus(options?: FocusOptions) {
this.button.focus(options);
}
- /** Removes focus from the icon button. */
+ /** @internal Removes focus from the icon button. */
blur() {
this.button.blur();
}
diff --git a/src/components/image-comparer/image-comparer.component.ts b/src/components/image-comparer/image-comparer.component.ts
index 9f9cebf9f9..3ce6cc36d8 100644
--- a/src/components/image-comparer/image-comparer.component.ts
+++ b/src/components/image-comparer/image-comparer.component.ts
@@ -41,7 +41,10 @@ export default class SlImageComparer extends ShoelaceElement {
private readonly localize = new LocalizeController(this);
+ /** @internal */
@query('.image-comparer') base: HTMLElement;
+
+ /** @internal */
@query('.image-comparer__handle') handle: HTMLElement;
/** The position of the divider as a percentage. */
@@ -90,6 +93,7 @@ export default class SlImageComparer extends ShoelaceElement {
}
}
+ /** @internal */
@watch('position', { waitUntilFirstUpdate: true })
handlePositionChange() {
this.emit('sl-change');
diff --git a/src/components/include/include.component.ts b/src/components/include/include.component.ts
index d4dbb2aba4..052bbd263d 100644
--- a/src/components/include/include.component.ts
+++ b/src/components/include/include.component.ts
@@ -42,6 +42,7 @@ export default class SlInclude extends ShoelaceElement {
script.parentNode!.replaceChild(newScript, script);
}
+ /** @internal */
@watch('src')
async handleSrcChange() {
try {
diff --git a/src/components/input/input.component.ts b/src/components/input/input.component.ts
index a672a8e06c..21d9f5279f 100644
--- a/src/components/input/input.component.ts
+++ b/src/components/input/input.component.ts
@@ -60,9 +60,12 @@ export default class SlInput extends ShoelaceElement implements ShoelaceFormCont
private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
private readonly localize = new LocalizeController(this);
+ /** @internal */
@query('.input__control') input: HTMLInputElement;
@state() private hasFocus = false;
+
+ /** @internal */
@property() title = ''; // make reactive to pass through
private __numberInput = Object.assign(document.createElement('input'), { type: 'number' });
@@ -303,12 +306,14 @@ export default class SlInput extends ShoelaceElement implements ShoelaceFormCont
this.passwordVisible = !this.passwordVisible;
}
+ /** @internal */
@watch('disabled', { waitUntilFirstUpdate: true })
handleDisabledChange() {
// Disabled form controls are always valid
this.formControlController.setValidity(this.disabled);
}
+ /** @internal */
@watch('step', { waitUntilFirstUpdate: true })
handleStepChange() {
// If step changes, the value may become invalid so we need to recheck after the update. We set the new step
@@ -317,18 +322,19 @@ export default class SlInput extends ShoelaceElement implements ShoelaceFormCont
this.formControlController.updateValidity();
}
+ /** @internal */
@watch('value', { waitUntilFirstUpdate: true })
async handleValueChange() {
await this.updateComplete;
this.formControlController.updateValidity();
}
- /** Sets focus on the input. */
+ /** @internal Sets focus on the input. */
focus(options?: FocusOptions) {
this.input.focus(options);
}
- /** Removes focus from the input. */
+ /** @internal Removes focus from the input. */
blur() {
this.input.blur();
}
diff --git a/src/components/menu-item/menu-item.component.ts b/src/components/menu-item/menu-item.component.ts
index 93d6f758f4..7d84e68404 100644
--- a/src/components/menu-item/menu-item.component.ts
+++ b/src/components/menu-item/menu-item.component.ts
@@ -50,7 +50,10 @@ export default class SlMenuItem extends ShoelaceElement {
private cachedTextLabel: string;
private readonly localize = new LocalizeController(this);
+ /** @internal */
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
+
+ /** @internal */
@query('.menu-item') menuItem: HTMLElement;
/** The type of menu item to render. To use `checked`, this value must be set to `checkbox`. */
@@ -112,6 +115,7 @@ export default class SlMenuItem extends ShoelaceElement {
event.stopPropagation();
};
+ /** @internal */
@watch('checked')
handleCheckedChange() {
// For proper accessibility, users have to use type="checkbox" to use the checked attribute
@@ -129,11 +133,13 @@ export default class SlMenuItem extends ShoelaceElement {
}
}
+ /** @internal */
@watch('disabled')
handleDisabledChange() {
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
}
+ /** @internal */
@watch('type')
handleTypeChange() {
if (this.type === 'checkbox') {
@@ -150,6 +156,7 @@ export default class SlMenuItem extends ShoelaceElement {
return getTextContent(this.defaultSlot);
}
+ /** @internal */
isSubmenu() {
return this.hasSlotController.test('submenu');
}
diff --git a/src/components/menu/menu.component.ts b/src/components/menu/menu.component.ts
index 5b94142885..98e03af70c 100644
--- a/src/components/menu/menu.component.ts
+++ b/src/components/menu/menu.component.ts
@@ -23,6 +23,7 @@ export interface MenuSelectEventDetail {
export default class SlMenu extends ShoelaceElement {
static styles: CSSResultGroup = [componentStyles, styles];
+ /** @internal */
@query('slot') defaultSlot: HTMLSlotElement;
connectedCallback() {
diff --git a/src/components/mutation-observer/mutation-observer.component.ts b/src/components/mutation-observer/mutation-observer.component.ts
index f78a9cf988..55770a3312 100644
--- a/src/components/mutation-observer/mutation-observer.component.ts
+++ b/src/components/mutation-observer/mutation-observer.component.ts
@@ -90,6 +90,7 @@ export default class SlMutationObserver extends ShoelaceElement {
this.mutationObserver.disconnect();
}
+ /** @internal */
@watch('disabled')
handleDisabledChange() {
if (this.disabled) {
@@ -99,6 +100,7 @@ export default class SlMutationObserver extends ShoelaceElement {
}
}
+ /** @internal */
@watch('attr', { waitUntilFirstUpdate: true })
@watch('attr-old-value', { waitUntilFirstUpdate: true })
@watch('char-data', { waitUntilFirstUpdate: true })
diff --git a/src/components/option/option.component.ts b/src/components/option/option.component.ts
index f3950cfeda..f34e245043 100644
--- a/src/components/option/option.component.ts
+++ b/src/components/option/option.component.ts
@@ -36,11 +36,17 @@ export default class SlOption extends ShoelaceElement {
private isInitialized = false;
+ /** @internal */
@query('.option__label') defaultSlot: HTMLSlotElement;
- @state() current = false; // the user has keyed into the option, but hasn't selected it yet (shows a highlight)
- @state() selected = false; // the option is selected and has aria-selected="true"
- @state() hasHover = false; // we need this because Safari doesn't honor :hover styles while dragging
+ /** @internal the user has keyed into the option, but hasn't selected it yet (shows a highlight) */
+ @state() current = false;
+
+ /** @internal the option is selected and has aria-selected="true" */
+ @state() selected = false;
+
+ /** @internal we need this because Safari doesn't honor :hover styles while dragging */
+ @state() hasHover = false;
/**
* The option's value. When selected, the containing form control will receive this value. The value must be unique
@@ -80,16 +86,19 @@ export default class SlOption extends ShoelaceElement {
this.hasHover = false;
}
+ /** @internal */
@watch('disabled')
handleDisabledChange() {
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
}
+ /** @internal */
@watch('selected')
handleSelectedChange() {
this.setAttribute('aria-selected', this.selected ? 'true' : 'false');
}
+ /** @internal */
@watch('value')
handleValueChange() {
// Ensure the value is a string. This ensures the next line doesn't error and allows framework users to pass numbers
diff --git a/src/components/progress-ring/progress-ring.component.ts b/src/components/progress-ring/progress-ring.component.ts
index fffd900dba..5ade4ffaaf 100644
--- a/src/components/progress-ring/progress-ring.component.ts
+++ b/src/components/progress-ring/progress-ring.component.ts
@@ -29,8 +29,10 @@ export default class SlProgressRing extends ShoelaceElement {
private readonly localize = new LocalizeController(this);
+ /** @internal */
@query('.progress-ring__indicator') indicator: SVGCircleElement;
+ /** @internal */
@state() indicatorOffset: string;
/** The current progress as a percentage, 0 to 100. */
diff --git a/src/components/qr-code/qr-code.component.ts b/src/components/qr-code/qr-code.component.ts
index f28ab50bb6..f03d6be040 100644
--- a/src/components/qr-code/qr-code.component.ts
+++ b/src/components/qr-code/qr-code.component.ts
@@ -19,6 +19,7 @@ import type { CSSResultGroup } from 'lit';
export default class SlQrCode extends ShoelaceElement {
static styles: CSSResultGroup = [componentStyles, styles];
+ /** @internal */
@query('canvas') canvas: HTMLElement;
/** The QR code's value. */
@@ -46,6 +47,7 @@ export default class SlQrCode extends ShoelaceElement {
this.generate();
}
+ /** @internal */
@watch(['background', 'errorCorrection', 'fill', 'radius', 'size', 'value'])
generate() {
if (!this.hasUpdated) {
diff --git a/src/components/radio-button/radio-button.component.ts b/src/components/radio-button/radio-button.component.ts
index dbd85a75f1..e062e14ade 100644
--- a/src/components/radio-button/radio-button.component.ts
+++ b/src/components/radio-button/radio-button.component.ts
@@ -34,7 +34,10 @@ export default class SlRadioButton extends ShoelaceElement {
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');
+ /** @internal */
@query('.button') input: HTMLInputElement;
+
+ /** @internal */
@query('.hidden-input') hiddenInput: HTMLInputElement;
@state() protected hasFocus = false;
@@ -85,17 +88,18 @@ export default class SlRadioButton extends ShoelaceElement {
this.emit('sl-focus');
}
+ /** @internal */
@watch('disabled', { waitUntilFirstUpdate: true })
handleDisabledChange() {
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
}
- /** Sets focus on the radio button. */
+ /** @internal Sets focus on the radio button. */
focus(options?: FocusOptions) {
this.input.focus(options);
}
- /** Removes focus from the radio button. */
+ /** @internal Removes focus from the radio button. */
blur() {
this.input.blur();
}
diff --git a/src/components/radio-group/radio-group.component.ts b/src/components/radio-group/radio-group.component.ts
index c1b4dc4bee..e924752fde 100644
--- a/src/components/radio-group/radio-group.component.ts
+++ b/src/components/radio-group/radio-group.component.ts
@@ -52,11 +52,16 @@ export default class SlRadioGroup extends ShoelaceElement implements ShoelaceFor
private customValidityMessage = '';
private validationTimeout: number;
+ /** @internal */
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
+
+ /** @internal */
@query('.radio-group__validation-input') validationInput: HTMLInputElement;
@state() private hasButtonGroup = false;
@state() private errorMessage = '';
+
+ /** @internal */
@state() defaultValue = '';
/**
@@ -261,11 +266,13 @@ export default class SlRadioGroup extends ShoelaceElement implements ShoelaceFor
this.formControlController.setValidity(this.validity.valid);
}
+ /** @internal */
@watch('size', { waitUntilFirstUpdate: true })
handleSizeChange() {
this.syncRadios();
}
+ /** @internal */
@watch('value')
handleValueChange() {
if (this.hasUpdated) {
@@ -318,7 +325,7 @@ export default class SlRadioGroup extends ShoelaceElement implements ShoelaceFor
this.formControlController.updateValidity();
}
- /** Sets focus on the radio-group. */
+ /** @internal Sets focus on the radio-group. */
public focus(options?: FocusOptions) {
const radios = this.getAllRadios();
const checked = radios.find(radio => radio.checked);
diff --git a/src/components/radio/radio.component.ts b/src/components/radio/radio.component.ts
index 3a3a0ceb22..b82ec15ebd 100644
--- a/src/components/radio/radio.component.ts
+++ b/src/components/radio/radio.component.ts
@@ -31,6 +31,7 @@ export default class SlRadio extends ShoelaceElement {
static styles: CSSResultGroup = [componentStyles, styles];
static dependencies = { 'sl-icon': SlIcon };
+ /** @internal */
@state() checked = false;
@state() protected hasFocus = false;
@@ -80,12 +81,14 @@ export default class SlRadio extends ShoelaceElement {
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
}
+ /** @internal */
@watch('checked')
handleCheckedChange() {
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
this.setAttribute('tabindex', this.checked ? '0' : '-1');
}
+ /** @internal */
@watch('disabled', { waitUntilFirstUpdate: true })
handleDisabledChange() {
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
diff --git a/src/components/range/range.component.ts b/src/components/range/range.component.ts
index c63fd4c0c7..6c12fab591 100644
--- a/src/components/range/range.component.ts
+++ b/src/components/range/range.component.ts
@@ -53,11 +53,16 @@ export default class SlRange extends ShoelaceElement implements ShoelaceFormCont
private readonly localize = new LocalizeController(this);
private resizeObserver: ResizeObserver;
+ /** @internal */
@query('.range__control') input: HTMLInputElement;
+
+ /** @internal */
@query('.range__tooltip') output: HTMLOutputElement | null;
@state() private hasFocus = false;
@state() private hasTooltip = false;
+
+ /** @internal */
@property() title = ''; // make reactive to pass through
/** The name of the range, submitted as a name/value pair with form data. */
@@ -191,6 +196,7 @@ export default class SlRange extends ShoelaceElement implements ShoelaceFormCont
}
}
+ /** @internal */
@watch('value', { waitUntilFirstUpdate: true })
handleValueChange() {
this.formControlController.updateValidity();
@@ -203,12 +209,14 @@ export default class SlRange extends ShoelaceElement implements ShoelaceFormCont
this.syncRange();
}
+ /** @internal */
@watch('disabled', { waitUntilFirstUpdate: true })
handleDisabledChange() {
// Disabled form controls are always valid
this.formControlController.setValidity(this.disabled);
}
+ /** @internal */
@watch('hasTooltip', { waitUntilFirstUpdate: true })
syncRange() {
const percent = Math.max(0, (this.value - this.min) / (this.max - this.min));
@@ -226,12 +234,12 @@ export default class SlRange extends ShoelaceElement implements ShoelaceFormCont
this.formControlController.emitInvalidEvent(event);
}
- /** Sets focus on the range. */
+ /** @internal Sets focus on the range. */
focus(options?: FocusOptions) {
this.input.focus(options);
}
- /** Removes focus from the range. */
+ /** @internal Removes focus from the range. */
blur() {
this.input.blur();
}