diff --git a/.changeset/tender-chairs-doubt.md b/.changeset/tender-chairs-doubt.md
new file mode 100644
index 0000000..a841bab
--- /dev/null
+++ b/.changeset/tender-chairs-doubt.md
@@ -0,0 +1,6 @@
+---
+"@scouterna/ui-react": patch
+"@scouterna/ui-webc": patch
+---
+
+Align all input components
diff --git a/.changeset/wicked-books-peel.md b/.changeset/wicked-books-peel.md
new file mode 100644
index 0000000..7749924
--- /dev/null
+++ b/.changeset/wicked-books-peel.md
@@ -0,0 +1,6 @@
+---
+"@scouterna/ui-react": minor
+"@scouterna/ui-webc": minor
+---
+
+Add radio button component
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..799c8ed
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,201 @@
+# Copilot Instructions for j26-components
+
+## Repository Overview
+
+This is the **Scouterna Design System Components** monorepo containing:
+- **Stencil Web Components** (packages/ui-webc) - source of truth for all components
+- **React Component Wrappers** (packages/ui-react) - auto-generated from Stencil
+- **Design Tokens** (packages/design-tokens) - CSS variables and theme values
+- **Storybook Documentation** (packages/storybook) - component demos and documentation
+
+**Languages**: TypeScript, CSS, React, Stencil
+**Tooling**: Biome (linting/formatting), Changesets (releases), Plop (generators)
+
+## Critical Build Requirements
+
+**ALWAYS run commands in this exact order to avoid build failures:**
+
+### 1. Initial Setup
+```bash
+pnpm install # Required before ANY other command
+```
+
+### 2. Full Build (required after changes)
+```bash
+pnpm build # Builds design-tokens → ui-webc → ui-react in dependency order
+```
+
+**Build sequence is critical:**
+- design-tokens MUST build first (generates CSS variables)
+- ui-webc depends on design-tokens being built
+- ui-react is auto-generated from ui-webc build output
+- Never run individual package builds unless explicitly testing
+
+### 3. Development Mode
+```bash
+pnpm dev # Runs all packages in watch mode, starts Storybook on :6006
+```
+
+### 4. Linting (CI runs this)
+```bash
+pnpm lint # Check for errors
+pnpm lint:fix # Auto-fix formatting/imports
+```
+
+**CI Requirement**: Code MUST pass `biome ci --error-on-warnings .` - warnings are treated as errors in CI.
+
+## Project Architecture & File Locations
+
+### Component Structure
+Each component lives in `packages/ui-webc/src/components/{name}/`:
+- `{name}.tsx` - Stencil component (source of truth)
+- `{name}.css` - Component styles (uses design tokens)
+- `readme.md` - **AUTO-GENERATED** documentation (never edit manually)
+
+Component naming convention: All components use `scout-{name}` tag name (e.g., `scout-button`)
+
+### Key Configuration Files
+- `/package.json` - Root workspace scripts
+- `/pnpm-workspace.yaml` - Monorepo workspace config
+- `/biome.json` - Linting and formatting rules
+- `/.node-version` - Node version (24.5)
+- `/.changeset/config.json` - Release configuration
+- `/packages/ui-webc/stencil.config.ts` - Stencil build config with React output target
+
+### Generated Files (Never Edit Directly)
+- `packages/ui-react/lib/components/stencil-generated/**` - Auto-generated from Stencil
+- `packages/ui-webc/src/components.d.ts` - Auto-generated type definitions
+- `packages/ui-webc/src/components/*/readme.md` - Auto-generated docs
+- `packages/design-tokens/dist/**` - Generated from tokens/ JSON files
+
+### Design Tokens
+Source: `packages/design-tokens/tokens/**/*.json`
+Output: `packages/design-tokens/dist/tokens.css` (imported in components)
+Build warnings about "Unknown CSS Font Shorthand properties" are **expected and safe to ignore**.
+
+## Component Development Workflow
+
+### Creating New Components
+```bash
+pnpm plop # Interactive generator creates all boilerplate
+```
+
+This creates:
+- Component in `packages/ui-webc/src/components/{name}/`
+- Storybook story in `packages/storybook/src/stories/{name}.stories.tsx`
+
+### Modifying Existing Components
+
+1. Edit source in `packages/ui-webc/src/components/{name}/{name}.tsx`
+2. Edit styles in `packages/ui-webc/src/components/{name}/{name}.css`
+3. **Rebuild**: `pnpm build` (triggers React wrapper regeneration)
+4. Test in Storybook: `pnpm dev` then visit http://localhost:6006
+5. Add changeset: `pnpm changeset` (required for releases)
+
+**Component Props**: Use JSDoc comments in .tsx file - they auto-generate into readme.md tables.
+
+### Form Input Components Pattern
+Input-like components (checkbox, radio-button, select, input) follow this pattern:
+- Must have `name` prop for form submission
+- Must have `value` prop
+- Must emit `scoutChecked` or `scoutInputChange` events with `{ checked/value, element }` payload
+- Must support `disabled` prop
+- Must emit internal `_fieldId` event for field association
+
+## Testing
+
+### Unit Tests (Stencil)
+```bash
+cd packages/ui-webc
+pnpm test # Runs spec and e2e tests with Stencil's test runner
+```
+
+Tests are rare in this codebase - only `utils.spec.ts` exists currently.
+
+### Visual Testing
+Use Storybook for manual visual testing. Build and preview with:
+```bash
+pnpm build # Build all packages first
+cd packages/storybook
+pnpm dev # Starts on port 6006
+```
+
+## Release Process (Changesets)
+
+**Every user-facing change REQUIRES a changeset:**
+
+1. After making changes, run:
+```bash
+pnpm changeset
+```
+
+2. Select affected packages (usually `@scouterna/ui-webc` and `@scouterna/ui-react`)
+3. Choose version bump type (patch/minor/major)
+4. Describe changes (appears in changelog)
+5. Commit the generated `.changeset/*.md` file with your changes
+
+**Important**: Regular commits without changesets don't appear in changelogs or trigger releases.
+
+## CI/CD Pipelines
+
+### Code Quality Check (runs on all pushes/PRs)
+- Workflow: `.github/workflows/code-quality.yml`
+- Runs: `biome ci --error-on-warnings .`
+- **Warnings = Failure** - must fix before merge
+
+### Release Process (main branch only)
+- Workflow: `.github/workflows/release.yml`
+- Triggered: On push to `main`
+- Actions:
+ 1. Runs `pnpm install`
+ 2. Runs `pnpm build`
+ 3. Creates release PR with version bumps (if changesets exist)
+ 4. Publishes to npm when release PR is merged
+ 5. Triggers Storybook deployment to GitHub Pages
+
+### Storybook Deployment
+- Workflow: `.github/workflows/deploy-github-pages.yml`
+- Builds Storybook from `packages/storybook/storybook-static`
+- Deploys to GitHub Pages
+
+**To replicate CI locally:**
+```bash
+pnpm install
+pnpm build
+biome ci --error-on-warnings .
+```
+
+## Common Pitfalls & Workarounds
+
+1. **"Cannot find module" errors**: Run `pnpm install && pnpm build` - React types won't exist until Stencil builds.
+
+2. **Storybook shows old component**: After editing a component, MUST run `pnpm build` to regenerate React wrappers before Storybook reflects changes.
+
+3. **Biome errors about imports**: Run `pnpm lint:fix` - Biome auto-organizes imports.
+
+4. **Design token warnings**: Warnings about "Unknown CSS Font Shorthand properties" during token build are expected and can be ignored.
+
+5. **Component readme.md out of sync**: These are auto-generated - edit JSDoc in the .tsx file, then rebuild.
+
+6. **pnpm workspace resolution failures**: Ensure packages are built in order. Root `pnpm build` handles this automatically.
+
+## PR Review Guidelines
+
+### Component Documentation
+
+**DO NOT** suggest adding comments to the props and events tables in component readme.md files. These tables are auto-generated from the component source code and should remain comment-free to maintain consistency with the generation process.
+
+Example of tables where comments should NOT be suggested:
+- Props tables showing component properties
+- Events tables documenting component events
+
+The documentation for these items should be maintained in the source code itself, not in the generated tables.
+
+## Trust These Instructions
+
+These instructions are comprehensive and tested. Only search for additional information if:
+- Instructions are incomplete for your specific task
+- Instructions are found to be incorrect
+- You need to understand implementation details not covered here
+
+For component changes, always check existing components (e.g., button, checkbox) as reference implementations before searching extensively.
diff --git a/packages/storybook/src/stories/radio-button.stories.tsx b/packages/storybook/src/stories/radio-button.stories.tsx
new file mode 100644
index 0000000..f9fdeb7
--- /dev/null
+++ b/packages/storybook/src/stories/radio-button.stories.tsx
@@ -0,0 +1,25 @@
+import { ScoutRadioButton } from "@scouterna/ui-react";
+import preview from "#.storybook/preview";
+
+const meta = preview.meta({
+ title: "Interaction/Radio Button",
+ component: ScoutRadioButton,
+ parameters: {
+ layout: "centered",
+ },
+});
+
+export default meta;
+
+export const BasicExample = meta.story({
+ args: {
+ name: "my-radio-group",
+ },
+ render: (args) => (
+
+
+
+
+
+ ),
+});
diff --git a/packages/ui-react/lib/components/stencil-generated/components.ts b/packages/ui-react/lib/components/stencil-generated/components.ts
index 19520dc..73d8c96 100644
--- a/packages/ui-react/lib/components/stencil-generated/components.ts
+++ b/packages/ui-react/lib/components/stencil-generated/components.ts
@@ -7,7 +7,7 @@
/* eslint-disable */
-import { type ScoutCheckboxCustomEvent, type ScoutInputCustomEvent, type ScoutLinkCustomEvent, type ScoutSelectCustomEvent, type ScoutSwitchCustomEvent } from "@scouterna/ui-webc";
+import { type ScoutCheckboxCustomEvent, type ScoutInputCustomEvent, type ScoutLinkCustomEvent, type ScoutRadioButtonCustomEvent, type ScoutSelectCustomEvent, type ScoutSwitchCustomEvent } from "@scouterna/ui-webc";
import { ScoutBottomBarItem as ScoutBottomBarItemElement, defineCustomElement as defineScoutBottomBarItem } from "@scouterna/ui-webc/dist/components/scout-bottom-bar-item.js";
import { ScoutBottomBar as ScoutBottomBarElement, defineCustomElement as defineScoutBottomBar } from "@scouterna/ui-webc/dist/components/scout-bottom-bar.js";
import { ScoutButton as ScoutButtonElement, defineCustomElement as defineScoutButton } from "@scouterna/ui-webc/dist/components/scout-button.js";
@@ -21,6 +21,7 @@ import { ScoutListViewItem as ScoutListViewItemElement, defineCustomElement as d
import { ScoutListViewSubheader as ScoutListViewSubheaderElement, defineCustomElement as defineScoutListViewSubheader } from "@scouterna/ui-webc/dist/components/scout-list-view-subheader.js";
import { ScoutListView as ScoutListViewElement, defineCustomElement as defineScoutListView } from "@scouterna/ui-webc/dist/components/scout-list-view.js";
import { ScoutLoader as ScoutLoaderElement, defineCustomElement as defineScoutLoader } from "@scouterna/ui-webc/dist/components/scout-loader.js";
+import { ScoutRadioButton as ScoutRadioButtonElement, defineCustomElement as defineScoutRadioButton } from "@scouterna/ui-webc/dist/components/scout-radio-button.js";
import { ScoutSelect as ScoutSelectElement, defineCustomElement as defineScoutSelect } from "@scouterna/ui-webc/dist/components/scout-select.js";
import { ScoutStack as ScoutStackElement, defineCustomElement as defineScoutStack } from "@scouterna/ui-webc/dist/components/scout-stack.js";
import { ScoutSwitch as ScoutSwitchElement, defineCustomElement as defineScoutSwitch } from "@scouterna/ui-webc/dist/components/scout-switch.js";
@@ -73,7 +74,7 @@ export const ScoutCard: StencilReactComponent
});
export type ScoutCheckboxEvents = {
- onScoutCheckboxChecked: EventName>,
@@ -86,7 +87,7 @@ export const ScoutCheckbox: StencilReactComponent>,
+ on_fieldId: EventName>
+};
+
+export const ScoutRadioButton: StencilReactComponent = /*@__PURE__*/ createComponent({
+ tagName: 'scout-radio-button',
+ elementClass: ScoutRadioButtonElement,
+ // @ts-ignore - ignore potential React type mismatches between the Stencil Output Target and your project.
+ react: React,
+ events: {
+ onScoutChecked: 'scoutChecked',
+ on_fieldId: '_fieldId'
+ } as ScoutRadioButtonEvents,
+ defineCustomElement: defineScoutRadioButton
+});
+
export type ScoutSelectEvents = {
onScoutInputChange: EventName extends CustomEvent {
detail: T;
target: HTMLScoutListViewItemElement;
}
+export interface ScoutRadioButtonCustomEvent extends CustomEvent {
+ detail: T;
+ target: HTMLScoutRadioButtonElement;
+}
export interface ScoutSelectCustomEvent extends CustomEvent {
detail: T;
target: HTMLScoutSelectElement;
@@ -325,7 +350,7 @@ declare global {
new (): HTMLScoutCardElement;
};
interface HTMLScoutCheckboxElementEventMap {
- "scoutCheckboxChecked": {
+ "scoutChecked": {
checked: boolean;
element: HTMLInputElement;
};
@@ -431,6 +456,27 @@ declare global {
prototype: HTMLScoutLoaderElement;
new (): HTMLScoutLoaderElement;
};
+ interface HTMLScoutRadioButtonElementEventMap {
+ "scoutChecked": {
+ checked: boolean;
+ element: HTMLInputElement;
+ };
+ "_fieldId": string;
+ }
+ interface HTMLScoutRadioButtonElement extends Components.ScoutRadioButton, HTMLStencilElement {
+ addEventListener(type: K, listener: (this: HTMLScoutRadioButtonElement, ev: ScoutRadioButtonCustomEvent) => any, options?: boolean | AddEventListenerOptions): void;
+ addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
+ addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
+ removeEventListener(type: K, listener: (this: HTMLScoutRadioButtonElement, ev: ScoutRadioButtonCustomEvent) => any, options?: boolean | EventListenerOptions): void;
+ removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
+ removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
+ }
+ var HTMLScoutRadioButtonElement: {
+ prototype: HTMLScoutRadioButtonElement;
+ new (): HTMLScoutRadioButtonElement;
+ };
interface HTMLScoutSelectElementEventMap {
"scoutInputChange": {
value: string;
@@ -494,6 +540,7 @@ declare global {
"scout-list-view-item": HTMLScoutListViewItemElement;
"scout-list-view-subheader": HTMLScoutListViewSubheaderElement;
"scout-loader": HTMLScoutLoaderElement;
+ "scout-radio-button": HTMLScoutRadioButtonElement;
"scout-select": HTMLScoutSelectElement;
"scout-stack": HTMLScoutStackElement;
"scout-switch": HTMLScoutSwitchElement;
@@ -577,7 +624,8 @@ declare namespace LocalJSX {
*/
"disabled"?: boolean;
"label"?: string;
- "onScoutCheckboxChecked"?: (event: ScoutCheckboxCustomEvent<{
+ "name"?: string;
+ "onScoutChecked"?: (event: ScoutCheckboxCustomEvent<{
checked: boolean;
element: HTMLInputElement;
}>) => void;
@@ -585,6 +633,7 @@ declare namespace LocalJSX {
* Internal event used for form field association.
*/
"on_fieldId"?: (event: ScoutCheckboxCustomEvent) => void;
+ "value"?: string;
}
interface ScoutDivider {
}
@@ -608,6 +657,7 @@ declare namespace LocalJSX {
* Input mode hints for devices with dynamic keyboards.
*/
"inputmode"?: InputMode;
+ "name"?: string;
"onScoutBlur"?: (event: ScoutInputCustomEvent) => void;
"onScoutInputChange"?: (event: ScoutInputCustomEvent<{
value: string;
@@ -694,12 +744,38 @@ declare namespace LocalJSX {
"size"?: "xs" | "sm" | "base" | "lg" | "xl";
"text"?: string;
}
+ interface ScoutRadioButton {
+ /**
+ * Use this prop if you need to connect your radio button with another element describing its use, other than the property label.
+ */
+ "ariaLabelledby"?: string;
+ /**
+ * @default false
+ */
+ "checked"?: boolean;
+ /**
+ * @default false
+ */
+ "disabled"?: boolean;
+ "label"?: string;
+ "name"?: string;
+ "onScoutChecked"?: (event: ScoutRadioButtonCustomEvent<{
+ checked: boolean;
+ element: HTMLInputElement;
+ }>) => void;
+ /**
+ * Internal event used for form field association.
+ */
+ "on_fieldId"?: (event: ScoutRadioButtonCustomEvent) => void;
+ "value"?: string;
+ }
interface ScoutSelect {
/**
* Whether the select is disabled. Disabled selects are not editable, excluded from tab order and are not validated.
* @default false
*/
"disabled"?: boolean;
+ "name"?: string;
"onScoutBlur"?: (event: ScoutSelectCustomEvent) => void;
"onScoutInputChange"?: (event: ScoutSelectCustomEvent<{
value: string;
@@ -769,6 +845,7 @@ declare namespace LocalJSX {
"scout-list-view-item": ScoutListViewItem;
"scout-list-view-subheader": ScoutListViewSubheader;
"scout-loader": ScoutLoader;
+ "scout-radio-button": ScoutRadioButton;
"scout-select": ScoutSelect;
"scout-stack": ScoutStack;
"scout-switch": ScoutSwitch;
@@ -805,6 +882,7 @@ declare module "@stencil/core" {
"scout-list-view-item": LocalJSX.ScoutListViewItem & JSXBase.HTMLAttributes;
"scout-list-view-subheader": LocalJSX.ScoutListViewSubheader & JSXBase.HTMLAttributes;
"scout-loader": LocalJSX.ScoutLoader & JSXBase.HTMLAttributes;
+ "scout-radio-button": LocalJSX.ScoutRadioButton & JSXBase.HTMLAttributes;
"scout-select": LocalJSX.ScoutSelect & JSXBase.HTMLAttributes;
"scout-stack": LocalJSX.ScoutStack & JSXBase.HTMLAttributes;
"scout-switch": LocalJSX.ScoutSwitch & JSXBase.HTMLAttributes;
diff --git a/packages/ui-webc/src/components/checkbox/checkbox.css b/packages/ui-webc/src/components/checkbox/checkbox.css
index 9c73aaf..6580c6b 100644
--- a/packages/ui-webc/src/components/checkbox/checkbox.css
+++ b/packages/ui-webc/src/components/checkbox/checkbox.css
@@ -24,7 +24,7 @@
.checkbox:checked:hover {
background-color: var(--color-background-brand-hovered);
- border: 2px solid var(--color-background-brand-hovered);
+ border-color: var(--color-background-brand-hovered);
box-shadow: none;
}
@@ -67,12 +67,8 @@
label {
display: flex;
- flex-direction: row-reverse;
align-items: center;
+ gap: var(--spacing-2);
font: var(--type-label-base);
color: var(--color-text-base);
}
-
-.inlineDivider {
- width: var(--spacing-2);
-}
diff --git a/packages/ui-webc/src/components/checkbox/checkbox.tsx b/packages/ui-webc/src/components/checkbox/checkbox.tsx
index cb02a3d..fd956ce 100644
--- a/packages/ui-webc/src/components/checkbox/checkbox.tsx
+++ b/packages/ui-webc/src/components/checkbox/checkbox.tsx
@@ -25,9 +25,13 @@ export class ScoutCheckbox {
@Prop() label: string;
+ @Prop() value: string;
+
+ @Prop() name: string;
+
@State() ariaId: string;
- @Event() scoutCheckboxChecked: EventEmitter<{
+ @Event() scoutChecked: EventEmitter<{
checked: boolean;
element: HTMLInputElement;
}>;
@@ -41,38 +45,33 @@ export class ScoutCheckbox {
this._fieldId.emit(this.ariaId);
}
- onClick(event: Event) {
+ onChange(event: Event) {
const checkbox = event.target as HTMLInputElement;
- console.log("checkbox", checkbox.checked);
- this.scoutCheckboxChecked.emit({
+ this.scoutChecked.emit({
checked: checkbox.checked,
element: checkbox,
});
}
- /*
- todo:
- - Wrap checkbox with label if used.
- - make sure it works with field nicely with label.
- */
render() {
const Tag = this.label?.length ? "label" : "div";
return (
- {this.label}
-
this.onClick(event)}
style={{ "--icon-checkbox": `url(${checkIcon})` }}
- type="checkbox"
- id={this.ariaId}
aria-labelledby={this.ariaLabelledby}
aria-disabled={this.disabled}
disabled={this.disabled}
checked={this.checked}
+ onChange={(event) => this.onChange(event)}
/>
+ {this.label}
);
}
diff --git a/packages/ui-webc/src/components/checkbox/readme.md b/packages/ui-webc/src/components/checkbox/readme.md
index 15fab21..b8f5f79 100644
--- a/packages/ui-webc/src/components/checkbox/readme.md
+++ b/packages/ui-webc/src/components/checkbox/readme.md
@@ -11,14 +11,16 @@
| `checked` | `checked` | | `boolean` | `false` |
| `disabled` | `disabled` | | `boolean` | `false` |
| `label` | `label` | | `string` | `undefined` |
+| `name` | `name` | | `string` | `undefined` |
+| `value` | `value` | | `string` | `undefined` |
## Events
-| Event | Description | Type |
-| ---------------------- | ----------------------------------------------- | --------------------------------------------------------------- |
-| `_fieldId` | Internal event used for form field association. | `CustomEvent` |
-| `scoutCheckboxChecked` | | `CustomEvent<{ checked: boolean; element: HTMLInputElement; }>` |
+| Event | Description | Type |
+| -------------- | ----------------------------------------------- | --------------------------------------------------------------- |
+| `_fieldId` | Internal event used for form field association. | `CustomEvent` |
+| `scoutChecked` | | `CustomEvent<{ checked: boolean; element: HTMLInputElement; }>` |
----------------------------------------------
diff --git a/packages/ui-webc/src/components/input/input.tsx b/packages/ui-webc/src/components/input/input.tsx
index 1e648ab..9df7d3c 100644
--- a/packages/ui-webc/src/components/input/input.tsx
+++ b/packages/ui-webc/src/components/input/input.tsx
@@ -58,6 +58,8 @@ export class ScoutInput implements ComponentInterface {
*/
@Prop() value: string = "";
+ @Prop() name: string;
+
/**
* Whether the input is disabled. Disabled inputs are not editable, excluded
* from tab order and are not validated.
@@ -108,6 +110,7 @@ export class ScoutInput implements ComponentInterface {
string` | `undefined` |
diff --git a/packages/ui-webc/src/components/radio-button/radio-button.css b/packages/ui-webc/src/components/radio-button/radio-button.css
new file mode 100644
index 0000000..6af96e4
--- /dev/null
+++ b/packages/ui-webc/src/components/radio-button/radio-button.css
@@ -0,0 +1,72 @@
+.radio {
+ width: var(--spacing-6);
+ height: var(--spacing-6);
+ appearance: none;
+ -webkit-appearance: none;
+ display: flex;
+ align-content: center;
+ justify-content: center;
+ border-radius: 100%;
+ background-color: var(--color-white);
+ border: 2px solid var(--color-gray-300);
+ position: relative;
+}
+
+.radio:hover {
+ border: 2px solid var(--color-gray-400);
+ box-shadow: inset 0px 0px 5px 5px var(--color-background-brand-subtle-hovered);
+ cursor: pointer;
+}
+
+.radio:active {
+ background-color: var(--color-background-brand-subtle-pressed);
+}
+
+.radio:checked:hover {
+ border-color: var(--color-background-brand-hovered);
+ box-shadow: none;
+}
+
+.radio:checked:hover::before {
+ background-color: var(--color-background-brand-hovered);
+}
+
+.radio:checked {
+ border-color: var(--color-background-brand-base);
+}
+
+.radio::after {
+ content: "";
+ position: absolute;
+ width: var(--spacing-10);
+ height: var(--spacing-10);
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.radio:checked::before {
+ content: "";
+ background-color: var(--color-background-brand-base);
+ width: var(--spacing-4);
+ height: var(--spacing-4);
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ border-radius: 100%;
+}
+
+.radio:disabled {
+ pointer-events: none;
+ background-color: var(--color-gray-100);
+ border-color: var(--color-gray-100);
+}
+
+label {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-2);
+ font: var(--type-label-base);
+ color: var(--color-text-base);
+}
diff --git a/packages/ui-webc/src/components/radio-button/radio-button.tsx b/packages/ui-webc/src/components/radio-button/radio-button.tsx
new file mode 100644
index 0000000..474d56b
--- /dev/null
+++ b/packages/ui-webc/src/components/radio-button/radio-button.tsx
@@ -0,0 +1,76 @@
+import {
+ Component,
+ Event,
+ type EventEmitter,
+ h,
+ Prop,
+ State,
+} from "@stencil/core";
+
+@Component({
+ tag: "scout-radio-button",
+ styleUrl: "radio-button.css",
+ scoped: true,
+})
+export class ScoutRadioButton {
+ @Prop() checked: boolean = false;
+
+ @Prop() disabled: boolean = false;
+
+ /**
+ * Use this prop if you need to connect your radio button with another element describing its use, other than the property label.
+ */
+ @Prop() ariaLabelledby: string;
+
+ @Prop() label: string;
+
+ @Prop() value: string;
+
+ @Prop() name: string;
+
+ @State() ariaId: string;
+
+ @Event() scoutChecked: EventEmitter<{
+ checked: boolean;
+ element: HTMLInputElement;
+ }>;
+ /**
+ * Internal event used for form field association.
+ */
+ @Event() _fieldId: EventEmitter;
+
+ componentWillLoad(): Promise | void {
+ this.ariaId = `_${Math.random().toString(36).substring(2, 9)}`;
+ this._fieldId.emit(this.ariaId);
+ }
+
+ onChange(event: Event) {
+ const radio = event.target as HTMLInputElement;
+
+ this.scoutChecked.emit({
+ checked: radio.checked,
+ element: radio,
+ });
+ }
+
+ render() {
+ const Tag = this.label?.length ? "label" : "div";
+ return (
+
+ this.onChange(event)}
+ />
+ {this.label}
+
+ );
+ }
+}
diff --git a/packages/ui-webc/src/components/radio-button/readme.md b/packages/ui-webc/src/components/radio-button/readme.md
new file mode 100644
index 0000000..974a26f
--- /dev/null
+++ b/packages/ui-webc/src/components/radio-button/readme.md
@@ -0,0 +1,28 @@
+# scout-radio-button
+
+
+
+
+## Properties
+
+| Property | Attribute | Description | Type | Default |
+| ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------- | ----------- |
+| `ariaLabelledby` | `aria-labelledby` | Use this prop if you need to connect your radio button with another element describing its use, other than the property label. | `string` | `undefined` |
+| `checked` | `checked` | | `boolean` | `false` |
+| `disabled` | `disabled` | | `boolean` | `false` |
+| `label` | `label` | | `string` | `undefined` |
+| `name` | `name` | | `string` | `undefined` |
+| `value` | `value` | | `string` | `undefined` |
+
+
+## Events
+
+| Event | Description | Type |
+| -------------- | ----------------------------------------------- | --------------------------------------------------------------- |
+| `_fieldId` | Internal event used for form field association. | `CustomEvent` |
+| `scoutChecked` | | `CustomEvent<{ checked: boolean; element: HTMLInputElement; }>` |
+
+
+----------------------------------------------
+
+*Built with [StencilJS](https://stenciljs.com/)*
diff --git a/packages/ui-webc/src/components/select/readme.md b/packages/ui-webc/src/components/select/readme.md
index 162c180..7e603b4 100644
--- a/packages/ui-webc/src/components/select/readme.md
+++ b/packages/ui-webc/src/components/select/readme.md
@@ -33,6 +33,7 @@ A styled native select component for choosing from a list of options.
| Property | Attribute | Description | Type | Default |
| ---------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ----------- |
| `disabled` | `disabled` | Whether the select is disabled. Disabled selects are not editable, excluded from tab order and are not validated. | `boolean` | `false` |
+| `name` | `name` | | `string` | `undefined` |
| `validate` | -- | Custom validation function run on top of the implicit validation performed by the browser. Return a string with the validation message to mark the select as invalid, or null to mark it as valid. | `(value: string) => string` | `undefined` |
| `value` | `value` | Value of the select element, in case you want to control it yourself. | `string` | `""` |
diff --git a/packages/ui-webc/src/components/select/select.tsx b/packages/ui-webc/src/components/select/select.tsx
index 40ece1d..58f1f92 100644
--- a/packages/ui-webc/src/components/select/select.tsx
+++ b/packages/ui-webc/src/components/select/select.tsx
@@ -26,6 +26,8 @@ export class ScoutSelect implements ComponentInterface {
*/
@Prop() disabled: boolean = false;
+ @Prop() name: string;
+
/**
* Custom validation function run on top of the implicit validation performed
* by the browser. Return a string with the validation message to mark the
@@ -70,6 +72,7 @@ export class ScoutSelect implements ComponentInterface {