Frameworks
React
Install and use WDS in a React app.
@wds/react has a typed React component for every wds-* element. A component sets properties on its element and passes the element's events to on<Event> props.
Installation#
Create project#
Skip this step if you already have a React app. The example uses Vite.
bash
npm create vite@latest my-app -- --template react-tsInstall packages#
@wds/core and lit are peer dependencies, so the app has one copy of each. A second @wds/core would define the same wds-* elements again.
pnpm add @wds/react @wds/core @wds/tokens litnpm install @wds/react @wds/core @wds/tokens lityarn add @wds/react @wds/core @wds/tokens litbun add @wds/react @wds/core @wds/tokens litImport the tokens#
Import them once, in the entry file.
main.tsx
// src/main.tsx
import '@wds/tokens/tokens.css';
import { createRoot } from 'react-dom/client';
import { App } from './App';
createRoot(document.getElementById('root')!).render(<App />);Use components#
Import a component from its entry point, such as @wds/react/button. The import also registers the element. An event such as wds-change is the onWdsChange prop.
App.tsx
import { useState } from 'react';
// Every component has its own entry point, so the bundle gets only what you use.
import { WdsButton } from '@wds/react/button';
import { WdsCard } from '@wds/react/card';
import { WdsFieldError } from '@wds/react/field';
import { WdsRadio, WdsRadioGroup } from '@wds/react/radio';
export function App() {
const [plan, setPlan] = useState('free');
const [busy, setBusy] = useState(false);
return (
<WdsCard heading="Plan">
{/* the wds-change event is the onWdsChange prop */}
<WdsRadioGroup value={plan} onWdsChange={(e) => setPlan(e.detail.value)}>
<WdsRadio value="free">Free</WdsRadio>
<WdsRadio value="pro">Pro</WdsRadio>
</WdsRadioGroup>
{/* booleans, arrays and objects reach the element as properties */}
<WdsFieldError errors={[{ message: 'Choose a plan' }]} />
<WdsButton slot="footer" loading={busy} onClick={() => setBusy(true)}>
Save
</WdsButton>
</WdsCard>
);
}Forms#
Form controls are form-associated, so they send their values with the form like native fields.
Signup.tsx
import { WdsButton } from '@wds/react/button';
import { WdsCheckbox } from '@wds/react/checkbox';
import { WdsInput } from '@wds/react/input';
export function Signup() {
async function action(data: FormData) {
// wds-* controls are in FormData like native fields
await fetch('/api/signup', { method: 'POST', body: data });
}
return (
<form action={action}>
<WdsInput name="email" type="email" required />
<WdsCheckbox name="newsletter">Newsletter</WdsCheckbox>
<WdsButton type="submit">Sign up</WdsButton>
</form>
);
}