Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions custom-elements-manifest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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`
})
]
};
1 change: 1 addition & 0 deletions docs/_includes/sidebar.njk
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<li><a href="/frameworks/vue">Vue</a></li>
<li><a href="/frameworks/angular">Angular</a></li>
<li><a href="/frameworks/svelte">Svelte</a></li>
<li><a href="/frameworks/jsx">JSX</a></li>
</ul>
</li>
<li>
Expand Down
155 changes: 155 additions & 0 deletions docs/pages/frameworks/jsx.md
Original file line number Diff line number Diff line change
@@ -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 (
<sl-card>
<h2 slot="header">Welcome to Shoelace</h2>
<p>This is a Shoelace card component used in JSX.</p>
<sl-button variant="primary" size="medium" onClick={handleClick}>
<sl-icon slot="prefix" name="star"></sl-icon>
Star
</sl-button>
</sl-card>
);
}
```

## 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 (
<div>
<sl-input placeholder="Type something..." onsl-input={handleInput} />

<sl-select placeholder="Choose an option" onsl-change={handleChange}>
<sl-option value="option1">Option 1</sl-option>
<sl-option value="option2">Option 2</sl-option>
<sl-option value="option3">Option 3</sl-option>
</sl-select>
</div>
);
}
```

## 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 (
<sl-dialog label="Dialog Title" open>
<p>This content goes in the default slot.</p>

<sl-button slot="footer" variant="default">
Cancel
</sl-button>
<sl-button slot="footer" variant="primary">
Confirm
</sl-button>
</sl-dialog>
);
}
```

## Type Safety

The JSX types provide full IntelliSense support and type checking:

```tsx
// ✅ Valid - all properties are properly typed
<sl-button
variant="primary" // Type: "default" | "primary" | "success" | "neutral" | "warning" | "danger" | "text"
size="large" // Type: "small" | "medium" | "large"
disabled={false} // Type: boolean
loading={true} // Type: boolean
/>

// ❌ Invalid - TypeScript will catch these errors
<sl-button
variant="invalid" // Error: Type '"invalid"' is not assignable
size="huge" // Error: Type '"huge"' is not assignable
disabled="false" // Error: Type 'string' is not assignable to type 'boolean'
/>
```
4 changes: 4 additions & 0 deletions docs/pages/frameworks/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/components/alert/alert.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
4 changes: 4 additions & 0 deletions src/components/animated-image/animated-image.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
1 change: 1 addition & 0 deletions src/components/animation/animation.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default class SlAnimation extends ShoelaceElement {
private animation?: Animation;
private hasStarted = false;

/** @internal */
@queryAsync('slot') defaultSlot: Promise<HTMLSlotElement>;

/** The name of the built-in animation to use. For custom animations, use the `keyframes` prop. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
3 changes: 3 additions & 0 deletions src/components/breadcrumb/breadcrumb.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
2 changes: 2 additions & 0 deletions src/components/button-group/button-group.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
5 changes: 5 additions & 0 deletions src/components/button/button.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
7 changes: 6 additions & 1 deletion src/components/carousel/carousel.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
2 changes: 2 additions & 0 deletions src/components/checkbox/checkbox.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
9 changes: 9 additions & 0 deletions src/components/color-picker/color-picker.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading