diff --git a/change/@fluentui-web-components-94ca1c7a-1462-4aa5-9458-41a54f17527e.json b/change/@fluentui-web-components-94ca1c7a-1462-4aa5-9458-41a54f17527e.json
new file mode 100644
index 00000000000000..35fa050bf1d102
--- /dev/null
+++ b/change/@fluentui-web-components-94ca1c7a-1462-4aa5-9458-41a54f17527e.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "feat(radio): add radio and radio-group web components",
+ "packageName": "@fluentui/web-components",
+ "email": "brianbrady@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/web-components/package.json b/packages/web-components/package.json
index ac7f56d7571515..c3280bf1a5e521 100644
--- a/packages/web-components/package.json
+++ b/packages/web-components/package.json
@@ -84,6 +84,14 @@
"types": "./dist/esm/progress-bar/define.d.ts",
"default": "./dist/esm/progress-bar/define.js"
},
+ "./radio": {
+ "types": "./dist/esm/radio/define.d.ts",
+ "default": "./dist/esm/radio/define.js"
+ },
+ "./radio-group": {
+ "types": "./dist/esm/radio-group/define.d.ts",
+ "default": "./dist/esm/radio-group/define.js"
+ },
"./slider": {
"types": "./dist/esm/slider/define.d.ts",
"default": "./dist/esm/slider/define.js"
diff --git a/packages/web-components/src/index.ts b/packages/web-components/src/index.ts
index cfbb551238a382..5207274c046849 100644
--- a/packages/web-components/src/index.ts
+++ b/packages/web-components/src/index.ts
@@ -13,6 +13,8 @@ export * from './menu-button/index.js';
export * from './menu-item/index.js';
export * from './menu-list/index.js';
export * from './progress-bar/index.js';
+export * from './radio/index.js';
+export * from './radio-group/index.js';
export * from './slider/index.js';
export * from './spinner/index.js';
export * from './switch/index.js';
diff --git a/packages/web-components/src/radio-group/README.md b/packages/web-components/src/radio-group/README.md
index d80d1ab434f637..b94bc67efda979 100644
--- a/packages/web-components/src/radio-group/README.md
+++ b/packages/web-components/src/radio-group/README.md
@@ -1,6 +1,6 @@
# Radio Group
-> RadioGroup lets people select a single option from two or more Radio items. Use RadioGroup to present all available choices if there's enough space..
+> RadioGroup lets users select a single option from two or more Radio items. Use RadioGroup to present all available choices if there's enough space..
@@ -36,13 +36,14 @@ Used anywhere an author might group a list of radio options.
### **Fields**
-| Name | Privacy | Type | Default | Description |
-| ------------- | ------- | ------------------------ | ------------ | ----------------------------------------------------------------------------------------------------- |
-| `disabled` | public | `boolean` | `false` | Disables the radio group and child radios. |
-| `name` | public | `string` | | The name of the radio group. Setting this value will set the name value for all child radio elements. |
-| `value` | public | `string` | | The value of the checked radio. |
-| `orientation` | public | `horizontal \| vertical` | `horizontal` | The orientation of the group |
-| default slot | public | `HTMLElement[]` | | The default slot expecting Radio items |
+| Name | Privacy | Type | Default | Description |
+| ------------- | ------- | ------------------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `disabled` | public | `boolean` | `false` | Disables the radio group and child radios. |
+| `name` | public | `string` | | The name of the radio group. Setting this value will set the name value for all child radio elements. |
+| `value` | public | `string` | | The value of the checked radio. |
+| `orientation` | public | `horizontal \| vertical` | `horizontal` | Determines whether radios in a radio group are rendered in a horizontal row or a vertical column. The default value is horizontal, which will render radios in a horizontal row with labels appearing inline. Setting orientation to vertical will render radios in a vertical column with labels appearing inline. |
+| `stacked` | public | `boolean` | `false` | Determines whether the labels for radios appear inline or stacked when orientation is set to horizontal. The default value is false, which will display the labels inline. If stacked is set to true, the labels will appear under each radio in a horizontal row. |
+| default slot | public | `HTMLElement[]` | | The default slot expecting Radio items. |
@@ -57,9 +58,9 @@ Used anywhere an author might group a list of radio options.
### **Events**
-| Name | Type | Description |
-| -------- | ---- | ---------------------------------------------------- |
-| `change` | | Fires a custom 'change' event when the value changes |
+| Name | Event Type | Target | Arguments | Description |
+| -------- | ------------- | ---------------- | --------- | ----------------------------------------------------------------------------------------------------------------- |
+| `change` | `CustomEvent` | `FASTRadioGroup` | none | Fired when the value of the RadioGroup changes (i.e., when a different radio button within the group is selected) |
@@ -97,9 +98,10 @@ Used anywhere an author might group a list of radio options.
### **WAI-ARIA Roles, States, and Properties**
-| Attributes | value | Description |
-| ----------------- | ----- | ---------------------------------------- |
-| `aria-labelledby` | | used to associate a label with the group |
+| Attributes | value | Description |
+| ----------------- | -------------- | ---------------------------------------- |
+| `aria-labelledby` | | used to associate a label with the group |
+| `role` | `"radiogroup"` | used to define a group of radio buttons |
@@ -122,6 +124,6 @@ Used anywhere an author might group a list of radio options.
**Property Mapping**
-| Fluent UI React 9 | Fluent Web Components 3 | Description of difference |
-|-------------------|------------------------ |---------------------------|
-| `layout` | `orientation` | React implementation requires user to pass either `"horizontal"` or `"horizontal-stacked"` through `layout` prop.
WC3 implementation requires user to either pass `"vertical"` or "`horizontal"` through `orientation` attribute.
+| Fluent UI React 9 | Fluent Web Components | Description of difference |
+|-------------------|-------------------------- |---------------------------|
+| `layout` | `orientation` + `stacked` | React implementation requires user to pass either `"horizontal"` or `"horizontal-stacked"` through `layout` prop.
WC3 implementation requires user to either pass `"vertical"` or "`horizontal"` through `orientation` attribute. Additionally, adding the `boolean` attribute `stacked` when the orientation is set to `horizontal` will create the `horizontal-stacked` layout available in FUIR9.
diff --git a/packages/web-components/src/radio-group/define.ts b/packages/web-components/src/radio-group/define.ts
new file mode 100644
index 00000000000000..2da64783f83419
--- /dev/null
+++ b/packages/web-components/src/radio-group/define.ts
@@ -0,0 +1,4 @@
+import { FluentDesignSystem } from '../fluent-design-system.js';
+import { definition } from './radio-group.definition.js';
+
+definition.define(FluentDesignSystem.registry);
diff --git a/packages/web-components/src/radio-group/index.ts b/packages/web-components/src/radio-group/index.ts
new file mode 100644
index 00000000000000..cfc435af8a7537
--- /dev/null
+++ b/packages/web-components/src/radio-group/index.ts
@@ -0,0 +1,5 @@
+export * from './radio-group.js';
+export { definition as RadioGroupDefinition } from './radio-group.definition.js';
+export { styles as RadioGroupStyles } from './radio-group.styles.js';
+export { template as RadioGroupTemplate } from './radio-group.template.js';
+export { RadioGroupOrientation } from '@microsoft/fast-foundation';
diff --git a/packages/web-components/src/radio-group/radio-group.definition.ts b/packages/web-components/src/radio-group/radio-group.definition.ts
new file mode 100644
index 00000000000000..0e3f17541c84b8
--- /dev/null
+++ b/packages/web-components/src/radio-group/radio-group.definition.ts
@@ -0,0 +1,18 @@
+import { FluentDesignSystem } from '../fluent-design-system.js';
+import { RadioGroup } from './radio-group.js';
+import { styles } from './radio-group.styles.js';
+import { template } from './radio-group.template.js';
+
+/**
+ * The Fluent RadioGroup Element.
+ *
+ *
+ * @public
+ * @remarks
+ * HTML Element: \
+ */
+export const definition = RadioGroup.compose({
+ name: `${FluentDesignSystem.prefix}-radio-group`,
+ template,
+ styles,
+});
diff --git a/packages/web-components/src/radio-group/radio-group.stories.ts b/packages/web-components/src/radio-group/radio-group.stories.ts
new file mode 100644
index 00000000000000..33d1eb816a9541
--- /dev/null
+++ b/packages/web-components/src/radio-group/radio-group.stories.ts
@@ -0,0 +1,215 @@
+import { html } from '@microsoft/fast-element';
+import type { Args, Meta } from '@storybook/html';
+import { RadioGroupOrientation } from '@microsoft/fast-foundation';
+import { renderComponent } from '../helpers.stories.js';
+import { RadioGroup as FluentRadioGroup } from './radio-group.js';
+import './define.js';
+import '../radio/define.js';
+
+type RadioGroupStoryArgs = Args & FluentRadioGroup;
+type RadioGroupStoryMeta = Meta;
+
+const storyTemplate = html`
+ x.disabled}
+ ?stacked=${x => x.stacked}
+ orientation=${x => x.orientation}
+ name="radio-story"
+ >
+ Favorite Fruit
+ x.checked}" value="apple">Apple
+ Pear
+ Banana
+ Orange
+
+`;
+
+export default {
+ title: 'Components/RadioGroup',
+ args: {
+ disabled: false,
+ orientation: RadioGroupOrientation.horizontal,
+ },
+ argTypes: {
+ disabled: {
+ control: {
+ type: 'boolean',
+ },
+ table: {
+ type: {
+ summary: 'Sets disabled state on radio',
+ },
+ defaultValue: {
+ summary: 'false',
+ },
+ },
+ },
+ checked: {
+ control: {
+ type: 'boolean',
+ },
+ table: {
+ type: {
+ summary: 'Sets checked state on radio',
+ },
+ defaultValue: {
+ summary: 'false',
+ },
+ },
+ },
+ stacked: {
+ control: {
+ type: 'boolean',
+ },
+ table: {
+ type: {
+ summary: 'Creates a stacked layout for horizontal radio buttons',
+ },
+ defaultValue: {
+ summary: 'false',
+ },
+ },
+ },
+ orientation: {
+ control: {
+ type: 'select',
+ options: Object.values(RadioGroupOrientation),
+ },
+ defaultValue: RadioGroupOrientation.horizontal,
+ table: {
+ type: {
+ summary: 'Sets orientation of radio group',
+ },
+ defaultValue: {
+ summary: RadioGroupOrientation.horizontal,
+ },
+ },
+ },
+ change: {
+ action: 'change',
+ table: {
+ type: {
+ summary: 'Event that is fired when the selected radio button changes',
+ },
+ defaultValue: {
+ summary: null,
+ },
+ },
+ },
+ },
+} as RadioGroupStoryMeta;
+
+export const RadioGroup = renderComponent(storyTemplate).bind({});
+
+export const RadioGroupLabelledby = renderComponent(html`
+
+ Favorite Fruit
+ Apple
+ Pear
+ Banana
+ Orange
+
+`);
+
+export const RadioGroupLayoutVertical = renderComponent(html`
+
+ Favorite Fruit
+ Apple
+ Pear
+ Banana
+ Orange
+
+`);
+
+export const RadioGroupLayoutHorizontal = renderComponent(html`
+
+ Favorite Fruit
+ Apple
+ Pear
+ Banana
+ Orange
+
+`);
+
+export const RadioGroupLayoutHorizontalStacked = renderComponent(html`
+
+ Favorite Fruit
+ Apple
+ Pear
+ Banana
+ Orange
+
+`);
+
+export const RadioGroupDefaultChecked = renderComponent(html`
+
+ Favorite Fruit
+ Apple
+ Pear
+ Banana
+ Orange
+
+`);
+
+export const RadioGroupDisabled = renderComponent(html`
+
+ Favorite Fruit
+ Apple
+ Pear
+ Banana
+ Orange
+
+`);
+
+export const RadioGroupDisabledItem = renderComponent(html`
+
+ Favorite Fruit
+ Apple
+ Pear
+ Banana
+ Orange
+
+`);
+
+const getLabelContent = (): string | undefined => {
+ const radioGroup = document.querySelector('#radio-group-fruit') as FluentRadioGroup;
+
+ if (!radioGroup) return; // add a check to make sure radioGroup exists
+
+ const selectedRadio = radioGroup.value as string;
+
+ if (selectedRadio) {
+ return `Favorite fruit: ${selectedRadio.charAt(0).toUpperCase() + selectedRadio.slice(1)}`;
+ } else {
+ return 'Please select your favorite fruit';
+ }
+};
+
+const handleChange = (event: CustomEvent) => {
+ const radioGroup = document.querySelector('#radio-group-fruit') as FluentRadioGroup;
+
+ if (!radioGroup) return; // add a check to make sure radioGroup exists
+
+ const selectedRadio = radioGroup.value as string;
+ const labelElement = radioGroup.querySelector('[slot="label"]') as HTMLSpanElement;
+ if (selectedRadio) {
+ const labelContent = selectedRadio.charAt(0).toUpperCase() + selectedRadio.slice(1);
+ labelElement.textContent = `Favorite fruit: ${labelContent}`;
+ }
+};
+
+export const RadioGroupChangeEvent = renderComponent(html`
+ handleChange(event)}"
+ >
+ ${getLabelContent}
+ Apple
+ Pear
+ Banana
+ Orange
+
+`);
diff --git a/packages/web-components/src/radio-group/radio-group.styles.ts b/packages/web-components/src/radio-group/radio-group.styles.ts
new file mode 100644
index 00000000000000..ec203c42a9a6ef
--- /dev/null
+++ b/packages/web-components/src/radio-group/radio-group.styles.ts
@@ -0,0 +1,62 @@
+import { css } from '@microsoft/fast-element';
+import { display } from '@microsoft/fast-foundation';
+import {
+ colorNeutralForeground1,
+ colorNeutralForegroundDisabled,
+ fontFamilyBase,
+ fontSizeBase300,
+ fontWeightRegular,
+ lineHeightBase300,
+ spacingHorizontalS,
+ spacingHorizontalXS,
+ spacingVerticalS,
+} from '../theme/design-tokens.js';
+
+/** RadioGroup styles
+ * @public
+ */
+export const styles = css`
+ ${display('flex')}
+
+ :host {
+ align-items: flex-start;
+ flex-direction: column;
+ row-gap: ${spacingVerticalS};
+ }
+ :host([disabled]) ::slotted([role='radio']) {
+ --control-border-color: ${colorNeutralForegroundDisabled};
+ --checked-indicator-background-color: ${colorNeutralForegroundDisabled};
+ --state-color: ${colorNeutralForegroundDisabled};
+ }
+ ::slotted([slot='label']) {
+ color: ${colorNeutralForeground1};
+ padding: ${spacingVerticalS} ${spacingHorizontalS} ${spacingVerticalS} ${spacingHorizontalXS};
+ font: ${fontWeightRegular} ${fontSizeBase300} / ${lineHeightBase300} ${fontFamilyBase};
+ cursor: default;
+ }
+ .positioning-region {
+ display: flex;
+ flex-wrap: wrap;
+ }
+ :host([orientation='vertical']) .positioning-region {
+ flex-direction: column;
+ justify-content: flex-start;
+ }
+ :host([orientation='horizontal']) .positioning-region {
+ flex-direction: row;
+ }
+ :host([orientation='horizontal']) ::slotted([role='radio']) {
+ padding-inline-end: ${spacingHorizontalS};
+ }
+ :host([orientation='horizontal'][stacked]) ::slotted([role='radio']) {
+ display: flex;
+ flex-direction: column;
+ padding-inline: ${spacingHorizontalS};
+ height: auto;
+ align-items: center;
+ justify-content: center;
+ }
+ :host([disabled]) ::slotted([role='radio']) {
+ pointer-events: none;
+ }
+`;
diff --git a/packages/web-components/src/radio-group/radio-group.template.ts b/packages/web-components/src/radio-group/radio-group.template.ts
new file mode 100644
index 00000000000000..5f884fd9e4c3b5
--- /dev/null
+++ b/packages/web-components/src/radio-group/radio-group.template.ts
@@ -0,0 +1,5 @@
+import type { ElementViewTemplate } from '@microsoft/fast-element';
+import { radioGroupTemplate } from '@microsoft/fast-foundation';
+import type { RadioGroup } from './radio-group.js';
+
+export const template: ElementViewTemplate = radioGroupTemplate();
diff --git a/packages/web-components/src/radio-group/radio-group.ts b/packages/web-components/src/radio-group/radio-group.ts
new file mode 100644
index 00000000000000..7ad958af222835
--- /dev/null
+++ b/packages/web-components/src/radio-group/radio-group.ts
@@ -0,0 +1,18 @@
+import { attr } from '@microsoft/fast-element';
+import { FASTRadioGroup } from '@microsoft/fast-foundation';
+
+/**
+ * The base class used for constructing a fluent-radio-group custom element
+ * @public
+ */
+export class RadioGroup extends FASTRadioGroup {
+ /**
+ * sets radio layout styles
+ *
+ * @public
+ * @remarks
+ * HTML Attribute: stacked
+ */
+ @attr({ mode: 'boolean' })
+ public stacked: boolean = false;
+}
diff --git a/packages/web-components/src/radio/README.md b/packages/web-components/src/radio/README.md
index 10152def1bbc1a..9193cfb46909a8 100644
--- a/packages/web-components/src/radio/README.md
+++ b/packages/web-components/src/radio/README.md
@@ -38,12 +38,11 @@ Used anywhere an author might otherwise use an input[type="radio"]. Used to faci
### **Fields**
-| Name | Privacy | Type | Default | Description |
-| --------------- | ------- | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
-| `name` | public | `string` | | The name of the radio. See [name attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname) for more info. |
-| `disabled` | public | `boolean` | | Sets disabled state for radio |
-| `labelPosition` | public | `"after"` `"below"` | `"below"` | The position of the label relative to the radio indicator. |
-| `checked` | public | `boolean` | `false` | When true, radio button will be checked |
+| Name | Privacy | Type | Default | Description |
+| ---------- | ------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `name` | public | `string` | | The name of the radio. See [name attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefname) for more info. When the `radio` component is rendered inside a `radio-group`, the `radio-group` overwrites the `name` in all its `radio` components. |
+| `disabled` | public | `boolean` | | Sets disabled state for radio |
+| `checked` | public | `boolean` | `false` | When true, radio button will be checked |
@@ -51,22 +50,13 @@ Used anywhere an author might otherwise use an input[type="radio"]. Used to faci
-### **Events**
-
-| Name | Type | Inherited From |
-| -------- | ---- | -------------- |
-| `change` | | |
-
-
-
### **Attributes**
-| Name | Field |
-| ---------------- | ------------- |
-| `name` | name |
-| `disabled` | disabled |
-| `label-position` | labelPosition |
-| `checked` | checked |
+| Name | Field |
+| ---------- | -------- |
+| `name` | name |
+| `disabled` | disabled |
+| `checked` | checked |
@@ -117,6 +107,16 @@ Used anywhere an author might otherwise use an input[type="radio"]. Used to faci
### **Fluent Web Component v3 v.s Fluent React 9**
+**Deltas**
+
+In contrast to the FUIRv9 implimentation of the `Radio` component the WC3 `Radio` must be rendered inside the WC3 `RadioGroup` to inherit all appropriate styles.
+
+```html
+
+
+
+```
+
**Component and Slot Mapping**
diff --git a/packages/web-components/src/radio/define.ts b/packages/web-components/src/radio/define.ts
new file mode 100644
index 00000000000000..66ca2a55ac25be
--- /dev/null
+++ b/packages/web-components/src/radio/define.ts
@@ -0,0 +1,4 @@
+import { FluentDesignSystem } from '../fluent-design-system.js';
+import { definition } from './radio.definition.js';
+
+definition.define(FluentDesignSystem.registry);
diff --git a/packages/web-components/src/radio/index.ts b/packages/web-components/src/radio/index.ts
new file mode 100644
index 00000000000000..e1af0f69389a11
--- /dev/null
+++ b/packages/web-components/src/radio/index.ts
@@ -0,0 +1,4 @@
+export * from './radio.js';
+export { definition as RadioDefinition } from './radio.definition.js';
+export { styles as RadioStyles } from './radio.styles.js';
+export { template as RadioTemplate } from './radio.template.js';
diff --git a/packages/web-components/src/radio/radio.definition.ts b/packages/web-components/src/radio/radio.definition.ts
new file mode 100644
index 00000000000000..479c8c1bb37c05
--- /dev/null
+++ b/packages/web-components/src/radio/radio.definition.ts
@@ -0,0 +1,18 @@
+import { FluentDesignSystem } from '../fluent-design-system.js';
+import { Radio } from './radio.js';
+import { styles } from './radio.styles.js';
+import { template } from './radio.template.js';
+
+/**
+ * The Fluent Radio Element.
+ *
+ *
+ * @public
+ * @remarks
+ * HTML Element: \
+ */
+export const definition = Radio.compose({
+ name: `${FluentDesignSystem.prefix}-radio`,
+ template,
+ styles,
+});
diff --git a/packages/web-components/src/radio/radio.stories.ts b/packages/web-components/src/radio/radio.stories.ts
new file mode 100644
index 00000000000000..52afdf568c0f21
--- /dev/null
+++ b/packages/web-components/src/radio/radio.stories.ts
@@ -0,0 +1,63 @@
+import { html } from '@microsoft/fast-element';
+import type { Args, Meta } from '@storybook/html';
+import { renderComponent } from '../helpers.stories.js';
+import type { Radio as FluentRadio } from './radio.js';
+import './define.js';
+import '../radio-group/define.js';
+
+type RadioStoryArgs = Args & FluentRadio;
+type RadioStoryMeta = Meta;
+
+const storyTemplate = html`
+
+`;
+
+export default {
+ title: 'Components/Radio',
+ args: {
+ checked: false,
+ disabled: false,
+ },
+ argTypes: {
+ checked: {
+ control: {
+ type: 'boolean',
+ },
+ table: {
+ type: {
+ summary: 'Sets checked state on radio',
+ },
+ defaultValue: {
+ summary: 'false',
+ },
+ },
+ },
+ disabled: {
+ control: {
+ type: 'boolean',
+ },
+ table: {
+ type: {
+ summary: 'Sets disabled state on radio',
+ },
+ defaultValue: {
+ summary: 'false',
+ },
+ },
+ },
+ },
+} as RadioStoryMeta;
+
+export const Radio = renderComponent(storyTemplate).bind({});
+
+export const Checked = renderComponent(html`
+ Apple
+`);
+
+export const Disabled = renderComponent(html`
+ Apple
+`);
diff --git a/packages/web-components/src/radio/radio.styles.ts b/packages/web-components/src/radio/radio.styles.ts
new file mode 100644
index 00000000000000..d80095a1894105
--- /dev/null
+++ b/packages/web-components/src/radio/radio.styles.ts
@@ -0,0 +1,130 @@
+import { css } from '@microsoft/fast-element';
+import { display } from '@microsoft/fast-foundation';
+import {
+ borderRadiusCircular,
+ borderRadiusSmall,
+ colorCompoundBrandForeground1,
+ colorCompoundBrandForeground1Pressed,
+ colorCompoundBrandStrokeHover,
+ colorCompoundBrandStrokePressed,
+ colorNeutralForeground2,
+ colorNeutralForeground3,
+ colorNeutralForegroundDisabled,
+ colorNeutralStrokeAccessible,
+ colorNeutralStrokeAccessibleHover,
+ colorNeutralStrokeAccessiblePressed,
+ colorStrokeFocus1,
+ colorStrokeFocus2,
+ fontFamilyBase,
+ fontSizeBase300,
+ fontWeightRegular,
+ lineHeightBase300,
+ spacingHorizontalS,
+ spacingHorizontalXS,
+ spacingVerticalS,
+} from '../theme/design-tokens.js';
+
+/** Radio styles
+ * @public
+ */
+export const styles = css`
+ ${display('inline-grid')}
+
+ :host {
+ grid-auto-flow: column;
+ grid-template-columns: max-content;
+ gap: ${spacingHorizontalXS};
+ align-items: center;
+ height: 32px;
+ cursor: pointer;
+ outline: none;
+ position: relative;
+ user-select: none;
+ color: blue;
+ color: var(--state-color, ${colorNeutralForeground3});
+ padding-inline-end: ${spacingHorizontalS};
+ --control-border-color: ${colorNeutralStrokeAccessible};
+ --checked-indicator-background-color: ${colorCompoundBrandForeground1};
+ --state-color: ${colorNeutralForeground3};
+ }
+ :host([disabled]) {
+ --control-border-color: ${colorNeutralForegroundDisabled};
+ --checked-indicator-background-color: ${colorNeutralForegroundDisabled};
+ --state-color: ${colorNeutralForegroundDisabled};
+ }
+ .label {
+ cursor: pointer;
+ font-family: ${fontFamilyBase};
+ font-size: ${fontSizeBase300};
+ font-weight: ${fontWeightRegular};
+ line-height: ${lineHeightBase300};
+ }
+ .label__hidden {
+ display: none;
+ }
+ .control {
+ box-sizing: border-box;
+ align-items: center;
+ border: 1px solid var(--control-border-color, ${colorNeutralStrokeAccessible});
+ border-radius: ${borderRadiusCircular};
+ display: flex;
+ height: 16px;
+ justify-content: center;
+ margin: ${spacingVerticalS} ${spacingHorizontalS};
+ position: relative;
+ width: 16px;
+ justify-self: center;
+ }
+ .checked-indicator {
+ border-radius: ${borderRadiusCircular};
+ height: 10px;
+ opacity: 0;
+ width: 10px;
+ }
+ :host([aria-checked='false']:hover) .control {
+ color: ${colorNeutralForeground2};
+ }
+ :host(:focus-visible) {
+ border-radius: ${borderRadiusSmall};
+ box-shadow: 0 0 0 3px ${colorStrokeFocus2};
+ outline: 1px solid ${colorStrokeFocus1};
+ }
+ :host(:hover) .control {
+ border-color: ${colorNeutralStrokeAccessibleHover};
+ }
+ :host(:active) .control {
+ border-color: ${colorNeutralStrokeAccessiblePressed};
+ }
+ :host([aria-checked='true']) .checked-indicator {
+ opacity: 1;
+ }
+ :host([aria-checked='true']) .control {
+ border-color: var(--control-border-color, ${colorNeutralStrokeAccessible});
+ }
+ :host([aria-checked='true']) .checked-indicator {
+ background-color: var(--checked-indicator-background-color, ${colorCompoundBrandForeground1});
+ }
+ :host([aria-checked='true']:hover) .control {
+ border-color: ${colorCompoundBrandStrokeHover};
+ }
+ :host([aria-checked='true']:hover) .checked-indicator {
+ background-color: ${colorCompoundBrandStrokeHover};
+ }
+ :host([aria-checked='true']:active) .control {
+ border-color: ${colorCompoundBrandStrokePressed};
+ }
+ :host([aria-checked='true']:active) .checked-indicator {
+ background: ${colorCompoundBrandForeground1Pressed};
+ }
+ :host([disabled]) {
+ color: ${colorNeutralForegroundDisabled};
+ pointer-events: none;
+ }
+ :host([disabled]) .control {
+ pointer-events: none;
+ border-color: ${colorNeutralForegroundDisabled};
+ }
+ :host([disabled]) .checked-indicator {
+ background: ${colorNeutralForegroundDisabled};
+ }
+`;
diff --git a/packages/web-components/src/radio/radio.template.ts b/packages/web-components/src/radio/radio.template.ts
new file mode 100644
index 00000000000000..5c387758c66704
--- /dev/null
+++ b/packages/web-components/src/radio/radio.template.ts
@@ -0,0 +1,7 @@
+import { ElementViewTemplate, html } from '@microsoft/fast-element';
+import { radioTemplate } from '@microsoft/fast-foundation';
+import type { Radio } from './radio.js';
+
+export const template: ElementViewTemplate = radioTemplate({
+ checkedIndicator: html``,
+});
diff --git a/packages/web-components/src/radio/radio.ts b/packages/web-components/src/radio/radio.ts
new file mode 100644
index 00000000000000..56c36b4198b26e
--- /dev/null
+++ b/packages/web-components/src/radio/radio.ts
@@ -0,0 +1,7 @@
+import { FASTRadio } from '@microsoft/fast-foundation';
+
+/**
+ * The base class used for constructing a fluent-radio custom element
+ * @public
+ */
+export class Radio extends FASTRadio {}