Switch
Switches enable users to instantly activate or deactivate features, settings, or options with immediate visual confirmation. The component features a sliding thumb that moves along a track to communicate the current state.
import {SwitchModule} from "@qualcomm-ui/angular/switch"Examples
Simple
The simple API bundles all subcomponents together into a single directive.
<label label="Label" q-switch></label>
Child Directives
Provide child directives to customize specific elements while keeping the simple API's default structure.
<label q-switch>
<div q-switch-label>Label</div>
</label>
Composite
Build with the composite API for granular control. This API requires you to provide each subcomponent, but gives you full control over the structure and layout.
<label q-switch-root>
<input q-switch-hidden-input />
<div q-switch-control></div>
<span q-switch-label>Label</span>
</label>
States
Based on the inputs, the switch will render as checked or unchecked.
<label defaultChecked label="Label" q-switch></label>
<label label="Label" q-switch></label>
Disabled
When disabled is true, the switch becomes non-interactive and is typically rendered with reduced opacity to indicate its unavailable state.
<label defaultChecked disabled label="Label" q-switch></label>
<label disabled label="Label" q-switch></label>
Sizes
Use the size input to change the size of the switch. The default size is md.
<label label="Small (sm)" q-switch size="sm"></label>
<label label="Medium (md)" q-switch size="md"></label>
<label label="Large (lg)" q-switch size="lg"></label>
Hint
Add a hint to provide additional context below the switch.
<label
hint="You can change this later"
label="Enable notifications"
q-switch
></label>
Group
Use SwitchGroup to group related switches with a shared label, hint text, and validation state.
Forms
Template Forms
- When using template-driven forms, add the required attribute to enable validation
- The errorText input (or
q-switch-error-textdirective) displays automatically when the field is invalid and the user has interacted with it
<label
errorText="Please accept the Terms of Service to continue"
label="Please accept the Terms of Service"
name="acceptTerms"
q-switch
required
[(ngModel)]="acceptTerms"
></label>
Reactive Forms
Use Reactive Forms for better control over form state and validation.
import {Component, inject} from "@angular/core"
import {FormBuilder, ReactiveFormsModule, Validators} from "@angular/forms"
import {ButtonModule} from "@qualcomm-ui/angular/button"
import {SwitchModule} from "@qualcomm-ui/angular/switch"
@Component({
imports: [SwitchModule, ReactiveFormsModule, ButtonModule],
selector: "switch-reactive-forms",
template: `
<form
class="flex w-56 flex-col gap-2"
[formGroup]="form"
(ngSubmit)="onSubmit()"
>
<label formControlName="acceptTerms" q-switch-root>
<input q-switch-hidden-input />
<div q-switch-control></div>
<span q-switch-label>Accept Terms of Service</span>
<span q-switch-error-text>
Please accept the Terms of Service to continue
</span>
</label>
<div class="mt-1 grid grid-cols-2 grid-rows-1 gap-3">
<button
emphasis="primary"
q-button
size="sm"
type="button"
variant="outline"
(click)="reset()"
>
Reset
</button>
<button
emphasis="primary"
q-button
size="sm"
type="submit"
variant="fill"
>
Submit
</button>
</div>
</form>
`,
})
export class SwitchReactiveFormsDemo {
private fb = inject(FormBuilder)
form = this.fb.group({
acceptTerms: [false, Validators.requiredTrue],
})
onSubmit() {
if (this.form.valid) {
console.log("Form submitted:", {
...this.form.value,
})
}
}
reset() {
this.form.reset({
acceptTerms: false,
})
}
}Multi-Field Validation
Reactive forms let you validate across multiple fields simultaneously, perfect for complex business rules like date ranges or dependent field relationships.
import {Component, inject, signal} from "@angular/core"
import {
type AbstractControl,
FormBuilder,
FormGroup,
ReactiveFormsModule,
type ValidationErrors,
type ValidatorFn,
} from "@angular/forms"
import {ButtonModule} from "@qualcomm-ui/angular/button"
import {ErrorTextComponent} from "@qualcomm-ui/angular/input"
import {SwitchModule} from "@qualcomm-ui/angular/switch"
// Custom validator that requires at least one switch to be selected
function atLeastOneSelectedValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!(control instanceof FormGroup)) {
return null
}
const values = Object.values(control.value)
const hasSelection = values.some((value) => value === true)
return hasSelection ? null : {atLeastOneRequired: true}
}
}
@Component({
imports: [
SwitchModule,
ReactiveFormsModule,
ButtonModule,
ErrorTextComponent,
],
selector: "switch-advanced-validation-demo",
template: `
<form
class="flex w-72 flex-col gap-2"
[formGroup]="form"
(ngSubmit)="onSubmit()"
>
<fieldset class="m-0 border-0 p-0">
<legend class="text-neutral-primary font-heading-xxs mb-3 p-0">
Select your interests (at least one required):
</legend>
<div class="flex flex-col gap-2" formGroupName="interests">
<label formControlName="technology" q-switch-root>
<input q-switch-hidden-input />
<div q-switch-control></div>
<span q-switch-label>Technology</span>
</label>
<label formControlName="sports" q-switch-root>
<input q-switch-hidden-input />
<div q-switch-control></div>
<span q-switch-label>Sports</span>
</label>
<label formControlName="music" q-switch-root>
<input q-switch-hidden-input />
<div q-switch-control></div>
<span q-switch-label>Music</span>
</label>
<label formControlName="travel" q-switch-root>
<input q-switch-hidden-input />
<div q-switch-control></div>
<span q-switch-label>Travel</span>
</label>
@if (interestsGroup.invalid && interestsGroup.touched) {
<div q-error-text>Please select at least one interest</div>
}
</div>
</fieldset>
<div class="mt-1 grid grid-cols-2 grid-rows-1 gap-3">
<button
emphasis="primary"
q-button
size="sm"
type="button"
variant="outline"
(click)="reset()"
>
Reset
</button>
<button
emphasis="primary"
q-button
size="sm"
type="submit"
variant="fill"
>
Submit
</button>
</div>
</form>
`,
})
export class SwitchAdvancedValidationDemo {
readonly formSubmitted = signal<boolean>(false)
private fb = inject(FormBuilder)
form = this.fb.group({
interests: this.fb.group(
{
music: false,
sports: false,
technology: false,
travel: false,
},
{validators: atLeastOneSelectedValidator()},
),
})
get interestsGroup() {
return this.form.get("interests")!
}
get selectedInterests() {
const interests = this.interestsGroup.value
return Object.entries(interests)
.filter(([_, selected]) => selected)
.map(([interest, _]) => interest)
}
onSubmit() {
this.formSubmitted.set(true)
if (this.form.valid) {
console.log("Form submitted:", {
...this.form.value,
selectedInterests: this.selectedInterests,
})
} else {
this.form.markAllAsTouched()
}
}
reset() {
this.form.reset({
interests: {
music: false,
sports: false,
technology: false,
travel: false,
},
})
this.formSubmitted.set(false)
}
}Composite Guidelines
Only bind form controls to the q-switch or q-switch-root directives.
<!-- Won't work ❌ -->
<div q-switch-root>
<input q-switch-hidden-input [(ngModel)]="value" />
<div q-switch-control></div>
</div>
<!-- Works as expected ✅ -->
<div q-switch-root [(ngModel)]="value">
<input q-switch-hidden-input />
<div q-switch-control></div>
</div>The composite elements are only intended to be used as direct descendants of the q-switch or q-switch-root directives.
<!-- Won't work alone ❌ -->
<input q-switch-hidden-input />
<div q-switch-control></div>
<!-- Works as expected ✅ -->
<div q-switch-root>
<input q-switch-hidden-input />
<div q-switch-control></div>
</div>Accessibility
- The root directive should always be attached to a
<label>element for accessibility:- it makes the entire component clickable, not just the input/control.
- it provides implicit association between the text and input element, which is ideal for screen readers.
- Always use the associated label or q-switch-label directive when authoring the text label. This is automatically associated with the input element.
- If you omit the visible label, give the switch an accessible name with aria-label or aria-labelledby on the simple
q-switchorq-switch-hidden-inputelement. Avoid[attr.aria-label]and[attr.aria-labelledby]; use[aria-label]or[aria-labelledby]for dynamic values.
<label aria-label="Airplane mode" q-switch></label>
Explorer
API
q-switch
The q-switch directive extends the q-switch-root directive with the following properties:
stringstringstring<label q-switch>
<div q-switch-error-text>...</div>
</label>
string<label q-switch>
<div q-switch-hint>...</div>
</label>
string<label q-switch>
<div q-switch-label>...</div>
</label>
Composite API
q-switch-root
boolean'ltr' | 'rtl'
boolean() =>
| Node
| ShadowRoot
| Document
stringbooleanindeterminate state.stringbooleanboolean| 'sm'
| 'md'
| 'lg'
stringbooleanclass'qui-switch__root'data-activedata-disableddata-focusdata-focus-visibledata-hoverdata-invaliddata-readonlydata-size| 'sm'
| 'md'
| 'lg'
data-state| 'checked'
| 'unchecked'
data-switch-part'root'q-switch-label
stringclass'qui-switch__label'data-activedata-disableddata-focusdata-focus-visibledata-hoverdata-invaliddata-readonlydata-size| 'sm'
| 'md'
| 'lg'
data-state| 'checked'
| 'unchecked'
data-switch-part'label'q-switch-hidden-input
stringstringstringclass'qui-switch__hidden-input'data-activedata-disableddata-focusdata-focus-visibledata-hoverdata-invaliddata-readonlydata-state| 'checked'
| 'unchecked'
data-switch-part'hidden-input'styleq-switch-control
class'qui-switch__control'data-activedata-disableddata-focusdata-focus-visibledata-hoverdata-invaliddata-readonlydata-size| 'sm'
| 'md'
| 'lg'
data-state| 'checked'
| 'unchecked'
data-switch-part'control'q-switch-thumb
class'qui-switch__thumb'data-activedata-disableddata-focusdata-focus-visibledata-hoverdata-invaliddata-readonlydata-size| 'sm'
| 'md'
| 'lg'
data-state| 'checked'
| 'unchecked'
data-switch-part'thumb'q-switch-hint
stringclass'qui-input__hint'data-activedata-disableddata-focusdata-focus-visibledata-hoverdata-invaliddata-readonlydata-state| 'checked'
| 'unchecked'
data-switch-part'hint'hiddenbooleanq-switch-error-text
| LucideIconData
| string
stringclass'qui-input__error-text'data-activedata-disableddata-focusdata-focus-visibledata-hoverdata-invaliddata-readonlydata-state| 'checked'
| 'unchecked'
data-switch-part'error-text'hiddenboolean