Skip to content
1 change: 1 addition & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1944,6 +1944,7 @@ ion-toggle,prop,justify,"end" | "space-between" | "start" | undefined,undefined,
ion-toggle,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
ion-toggle,prop,mode,"ios" | "md",undefined,false,false
ion-toggle,prop,name,string,this.inputId,false,false
ion-toggle,prop,required,boolean,false,false,false
ion-toggle,prop,value,null | string | undefined,'on',false,false
ion-toggle,event,ionBlur,void,true
ion-toggle,event,ionChange,ToggleChangeEventDetail<any>,true
Expand Down
8 changes: 8 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3280,6 +3280,10 @@ export namespace Components {
* The name of the control, which is submitted with the form data.
*/
"name": string;
/**
* If true, screen readers will announce it as a required field. This property works only for accessibility purposes, it will not prevent the form from submitting if the value is invalid.
*/
"required": boolean;
/**
* The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`.
*/
Expand Down Expand Up @@ -8155,6 +8159,10 @@ declare namespace LocalJSX {
* Emitted when the toggle has focus.
*/
"onIonFocus"?: (event: IonToggleCustomEvent<void>) => void;
/**
* If true, screen readers will announce it as a required field. This property works only for accessibility purposes, it will not prevent the form from submitting if the value is invalid.
*/
"required"?: boolean;
/**
* The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`.
*/
Expand Down
30 changes: 30 additions & 0 deletions core/src/components/toggle/test/toggle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,33 @@ describe('ion-toggle: disabled', () => {
expect(toggle.checked).toBe(false);
});
});

describe('ion-toggle: required', () => {
it('should have a required attribute in inner input when true', async () => {
const page = await newSpecPage({
components: [Toggle],
html: `
<ion-toggle required="true">Toggle</ion-toggle>
`,
});

const toggle = page.body.querySelector('ion-toggle')!;
const nativeInput = toggle.shadowRoot?.querySelector('input[role=switch]')!;

expect(nativeInput.hasAttribute('required')).toBeTruthy();
});

it('should not have a required attribute in inner input when false', async () => {
const page = await newSpecPage({
components: [Toggle],
html: `
<ion-toggle required="false">Toggle</ion-toggle>
`,
});

const toggle = page.body.querySelector('ion-toggle')!;
const nativeInput = toggle.shadowRoot?.querySelector('input[role=switch]')!;

expect(nativeInput.hasAttribute('required')).toBeFalsy();
});
});
11 changes: 10 additions & 1 deletion core/src/components/toggle/toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ export class Toggle implements ComponentInterface {
*/
@Prop() alignment?: 'start' | 'center';

/**
* If true, screen readers will announce it as a required field. This property
* works only for accessibility purposes, it will not prevent the form from
* submitting if the value is invalid.
*/
@Prop() required = false;

/**
* Emitted when the user switches the toggle on or off.
*
Expand Down Expand Up @@ -290,7 +297,8 @@ export class Toggle implements ComponentInterface {
}

render() {
const { activated, color, checked, disabled, el, justify, labelPlacement, inputId, name, alignment } = this;
const { activated, color, checked, disabled, el, justify, labelPlacement, inputId, name, alignment, required } =
this;

const mode = getIonMode(this);
const value = this.getValue();
Expand Down Expand Up @@ -327,6 +335,7 @@ export class Toggle implements ComponentInterface {
onFocus={() => this.onFocus()}
onBlur={() => this.onBlur()}
ref={(focusEl) => (this.focusEl = focusEl)}
required={required}
{...this.inheritedAttributes}
/>
<div
Expand Down
4 changes: 2 additions & 2 deletions packages/angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2473,14 +2473,14 @@ Shorthand for ionToastDidDismiss.


@ProxyCmp({
inputs: ['alignment', 'checked', 'color', 'disabled', 'enableOnOffLabels', 'justify', 'labelPlacement', 'mode', 'name', 'value']
inputs: ['alignment', 'checked', 'color', 'disabled', 'enableOnOffLabels', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'value']
})
@Component({
selector: 'ion-toggle',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['alignment', 'checked', 'color', 'disabled', 'enableOnOffLabels', 'justify', 'labelPlacement', 'mode', 'name', 'value'],
inputs: ['alignment', 'checked', 'color', 'disabled', 'enableOnOffLabels', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'value'],
})
export class IonToggle {
protected el: HTMLIonToggleElement;
Expand Down
1 change: 1 addition & 0 deletions packages/vue/src/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,7 @@ export const IonToggle = /*@__PURE__*/ defineContainer<JSX.IonToggle, JSX.IonTog
'labelPlacement',
'justify',
'alignment',
'required',
'ionChange',
'ionFocus',
'ionBlur'
Expand Down
Loading