Switch
A toggle switch component for boolean states
Import
import {
AvSwitchImports,
AvSwitchGroupComponent,
} from '@avesra/angular';Usage
import { Component } from '@angular/core';
import { AvSwitchImports } from '@avesra/angular';
@Component({
selector: 'app-switch-basic-demo',
imports: [AvSwitchImports],
template: `<div av-switch>
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Enable notifications</span>
</div>`,
})
export class SwitchBasicDemo {}Anatomy
Compose Switch with attribute selectors on av-switch, av-switch-control, av-switch-thumb, and av-switch-content. Optional av-switch-icon, av-description, and layout via av-switch-group.
<!-- Single switch -->
<div av-switch>
<span av-switch-control>
<span av-switch-thumb>
<!-- Optional icon -->
<span av-switch-icon></span>
</span>
</span>
<span av-switch-content>
Enable notifications
<!-- Optional — field-level help text -->
<p av-description></p>
</span>
</div>
<!-- Grouped switches -->
<av-switch-group>
<div av-switch>
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Option 1</span>
</div>
<div av-switch>
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Option 2</span>
</div>
</av-switch-group>Sizes
import { Component } from '@angular/core';
import { AvSwitchImports } from '@avesra/angular';
@Component({
selector: 'app-switch-sizes-demo',
imports: [AvSwitchImports],
template: `<div class="flex gap-6">
<div av-switch size="sm">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Small</span>
</div>
<div av-switch size="md">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Medium</span>
</div>
<div av-switch size="lg">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Large</span>
</div>
</div>`,
})
export class SwitchSizesDemo {}With Icons
Project icons into av-switch-icon inside the thumb. Bind [(selected)] to swap icons by state.
import { Component, signal } from '@angular/core';
import { AvSwitchImports } from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-switch-with-icons-demo',
imports: [
AvSwitchImports,
AppIconComponent,
],
template: `<div class="flex flex-wrap gap-3">
<div
av-switch
size="lg"
aria-label="check"
class="group"
[(selected)]="check"
>
<span av-switch-content>
<span
av-switch-control
class="group-data-[selected=true]:bg-green-500/80"
>
<span av-switch-thumb>
<span av-switch-icon>
<app-icon
[icon]="check() ? 'solar:check-circle-bold' : 'solar:power-linear'"
size="12"
class="opacity-100"
[class.opacity-70]="!check()"
/>
</span>
</span>
</span>
</span>
</div>
<div
av-switch
size="lg"
aria-label="darkMode"
class="group"
[(selected)]="darkMode"
>
<span av-switch-content>
<span av-switch-control>
<span av-switch-thumb>
<span av-switch-icon>
<app-icon
[icon]="darkMode() ? 'solar:sun-bold' : 'solar:moon-bold'"
size="12"
class="opacity-100"
[class.opacity-70]="!darkMode()"
/>
</span>
</span>
</span>
</span>
</div>
<div
av-switch
size="lg"
aria-label="microphone"
class="group"
[(selected)]="microphone"
>
<span av-switch-content>
<span
av-switch-control
class="group-data-[selected=true]:bg-red-500/80"
>
<span av-switch-thumb>
<span av-switch-icon>
<app-icon
[icon]="microphone() ? 'solar:microphone-3-bold' : 'solar:microphone-3-linear'"
size="12"
class="opacity-100"
[class.opacity-70]="!microphone()"
/>
</span>
</span>
</span>
</span>
</div>
<div
av-switch
size="lg"
aria-label="notification"
class="group"
[(selected)]="notification"
>
<span av-switch-content>
<span
av-switch-control
class="group-data-[selected=true]:bg-purple-500/80"
>
<span av-switch-thumb>
<span av-switch-icon>
<app-icon
[icon]="notification() ? 'solar:bell-bold' : 'solar:bell-off-linear'"
size="12"
class="opacity-100"
[class.opacity-70]="!notification()"
/>
</span>
</span>
</span>
</span>
</div>
<div
av-switch
size="lg"
aria-label="volume"
class="group"
[(selected)]="volume"
>
<span av-switch-content>
<span
av-switch-control
class="group-data-[selected=true]:bg-blue-500/80"
>
<span av-switch-thumb>
<span av-switch-icon>
<app-icon
[icon]="volume() ? 'solar:volume-loud-bold' : 'solar:volume-cross-bold'"
size="12"
class="opacity-100"
[class.opacity-70]="!volume()"
/>
</span>
</span>
</span>
</span>
</div>
</div>`,
})
export class SwitchWithIconsDemo {
readonly check = signal(true);
readonly darkMode = signal(true);
readonly microphone = signal(true);
readonly notification = signal(true);
readonly volume = signal(true);
}Disabled
import { Component } from '@angular/core';
import { AvSwitchImports } from '@avesra/angular';
@Component({
selector: 'app-switch-disabled-demo',
imports: [AvSwitchImports],
template: `<div av-switch disabled>
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Enable notifications</span>
</div>`,
})
export class SwitchDisabledDemo {}Without Label
Omit label text and pass aria-label on av-switch for an accessible name.
import { Component } from '@angular/core';
import { AvSwitchImports } from '@avesra/angular';
@Component({
selector: 'app-switch-without-label-demo',
imports: [AvSwitchImports],
template: `<div av-switch aria-label="Enable notifications">
<span av-switch-content>
<span av-switch-control>
<span av-switch-thumb></span>
</span>
</span>
</div>`,
})
export class SwitchWithoutLabelDemo {}With Description
Add av-description inside av-switch-content for field-level help text.
Allow others to see your profile information
import { Component } from '@angular/core';
import {
AvDescriptionComponent,
AvSwitchImports,
} from '@avesra/angular';
@Component({
selector: 'app-switch-with-description-demo',
imports: [
AvSwitchImports,
AvDescriptionComponent,
],
template: `<div class="max-w-sm">
<div av-switch>
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>
Public profile
<p av-description>Allow others to see your profile information</p>
</span>
</div>
</div>`,
})
export class SwitchWithDescriptionDemo {}Default Selected
import { Component } from '@angular/core';
import { AvSwitchImports } from '@avesra/angular';
@Component({
selector: 'app-switch-default-selected-demo',
imports: [AvSwitchImports],
template: `<div av-switch default-selected>
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Enable notifications</span>
</div>`,
})
export class SwitchDefaultSelectedDemo {}Controlled
Bind [(selected)] for two-way control over the on/off state.
Switch is off
import { Component, signal } from '@angular/core';
import { AvSwitchImports } from '@avesra/angular';
@Component({
selector: 'app-switch-controlled-demo',
imports: [AvSwitchImports],
template: `<div class="flex flex-col gap-4">
<div av-switch [(selected)]="isSelected">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Enable notifications</span>
</div>
<p class="text-sm text-muted">Switch is {{ isSelected() ? 'on' : 'off' }}</p>
</div>`,
})
export class SwitchControlledDemo {
readonly isSelected = signal(false);
}Label Position
Reorder av-switch-control and av-switch-content to place the label before or after the track.
import { Component } from '@angular/core';
import { AvSwitchImports } from '@avesra/angular';
@Component({
selector: 'app-switch-label-position-demo',
imports: [AvSwitchImports],
template: `<div class="flex flex-col gap-4">
<div av-switch>
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Label after</span>
</div>
<div av-switch>
<span av-switch-content>Label before</span>
<span av-switch-control>
<span av-switch-thumb></span>
</span>
</div>
</div>`,
})
export class SwitchLabelPositionDemo {}Group
Use av-switch-group to lay out multiple switches (vertical by default).
import { Component } from '@angular/core';
import {
AvSwitchImports,
AvSwitchGroupComponent,
} from '@avesra/angular';
@Component({
selector: 'app-switch-group-demo',
imports: [
AvSwitchGroupComponent,
AvSwitchImports,
],
template: `<av-switch-group>
<div av-switch name="notifications">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Allow Notifications</span>
</div>
<div av-switch name="marketing">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Marketing emails</span>
</div>
<div av-switch name="social">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Social media updates</span>
</div>
</av-switch-group>`,
})
export class SwitchGroupDemo {}Group Horizontal
Set orientation="horizontal" on av-switch-group.
import { Component } from '@angular/core';
import {
AvSwitchImports,
AvSwitchGroupComponent,
} from '@avesra/angular';
@Component({
selector: 'app-switch-group-horizontal-demo',
imports: [
AvSwitchGroupComponent,
AvSwitchImports,
],
template: `<av-switch-group orientation="horizontal" class="overflow-x-auto">
<div av-switch name="notifications">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Notifications</span>
</div>
<div av-switch name="marketing">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Marketing</span>
</div>
<div av-switch name="social">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Social</span>
</div>
</av-switch-group>`,
})
export class SwitchGroupHorizontalDemo {}Form Integration
Use name and value for native form submission. Switch also implements ControlValueAccessor for reactive forms via formControlName.
import { Component } from '@angular/core';
import {
AvButtonComponent,
AvSwitchImports,
AvSwitchGroupComponent,
} from '@avesra/angular';
@Component({
selector: 'app-switch-form-demo',
imports: [
AvSwitchGroupComponent,
AvSwitchImports,
AvButtonComponent,
],
template: `<form class="flex flex-col gap-4" (submit)="onSubmit($event)">
<av-switch-group>
<div av-switch name="notifications" value="on">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Enable notifications</span>
</div>
<div av-switch default-selected name="newsletter" value="on">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Subscribe to newsletter</span>
</div>
<div av-switch name="marketing" value="on">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Receive marketing updates</span>
</div>
</av-switch-group>
<button av-button class="mt-4" size="sm" type="submit" variant="primary">
Submit
</button>
</form>`,
})
export class SwitchFormDemo {
onSubmit(event: Event): void {
event.preventDefault();
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
const entries = Array.from(formData.entries())
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
alert(`Form submitted with:\n${entries}`);
}
}Render Props
Angular has no React-style render props. Bind [(selected)] and interpolate state in the template instead.
import { Component, signal } from '@angular/core';
import { AvSwitchImports } from '@avesra/angular';
@Component({
selector: 'app-switch-render-props-demo',
imports: [AvSwitchImports],
template: `<div av-switch [(selected)]="isSelected">
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>{{ isSelected() ? 'Enabled' : 'Disabled' }}</span>
</div>`,
})
export class SwitchRenderPropsDemo {
readonly isSelected = signal(false);
}Custom Styles
import { Component, signal } from '@angular/core';
import { AvSwitchImports } from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-switch-custom-styles-demo',
imports: [
AvSwitchImports,
AppIconComponent,
],
template: `<div av-switch aria-label="Power" class="group" [(selected)]="isSelected">
<span av-switch-content>
<span
av-switch-control
class="h-[31px] w-[51px] bg-blue-500 group-data-[selected=true]:bg-cyan-500 group-data-[selected=true]:shadow-[0_0_12px_rgba(6,182,212,0.5)]"
>
<span
av-switch-thumb
class="size-[27px] bg-white shadow-sm group-data-[selected=true]:ms-[22px] group-data-[selected=true]:shadow-lg"
>
<span av-switch-icon>
@if (isSelected()) {
<app-icon icon="solar:check-circle-bold" size="16" class="text-cyan-600" />
} @else {
<app-icon icon="solar:power-linear" size="16" class="text-blue-600" />
}
</span>
</span>
</span>
</span>
</div>`,
})
export class SwitchCustomStylesDemo {
readonly isSelected = signal(false);
}Styling
Passing Tailwind CSS classes
Pass utility classes on av-switch, av-switch-control, av-switch-thumb, and av-switch-group. Use group / group-data-[selected=true]: for selected-state utilities (see Custom Styles above), or customize the group layout:
<av-switch-group class="gap-8" orientation="horizontal">
<div av-switch>
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Option 1</span>
</div>
<div av-switch>
<span av-switch-control>
<span av-switch-thumb></span>
</span>
<span av-switch-content>Option 2</span>
</div>
</av-switch-group>Customizing the component classes
To customize the Switch classes, use the @layer components directive. Learn more.
@layer components {
.av-switch {
@apply inline-flex gap-3 items-center;
}
.av-switch__control {
@apply h-5 w-8 bg-gray-400 data-[selected=true]:bg-blue-500;
}
.av-switch__thumb {
@apply bg-white shadow-sm;
}
.av-switch__content {
@apply items-center gap-3;
}
.av-switch__icon {
@apply h-3 w-3 text-current;
}
}Avesra follows the BEM methodology so component variants and states stay reusable and easy to customize.
CSS Classes
Switch Classes
.av-switch— Base switch container.av-switch__content— Label and description container.av-switch__control— Switch control track.av-switch__thumb— Thumb that moves.av-switch__icon— Optional icon inside the thumb.av-switch__input— Visually hidden native input.av-switch--sm— Small size variant.av-switch--md— Medium size variant (default).av-switch--lg— Large size variant
SwitchGroup Classes
.av-switch-group— Switch group container.av-switch-group--horizontal— Horizontal layout.av-switch-group--vertical— Vertical layout (default)
Interactive States
The switch supports both CSS pseudo-classes and data attributes for flexibility:
- Selected:
[data-selected="true"]or[aria-checked="true"](thumb position and background color change) - Hover:
:hoveror[data-hovered="true"] - Focus:
:focus-visibleor[data-focus-visible="true"] - Disabled:
[data-disabled="true"]or[aria-disabled="true"] - Pressed:
:activeor[data-pressed="true"]
API
Switch
Props for div[av-switch]. Implements ControlValueAccessor for Angular forms. span[av-switch-control], span[av-switch-thumb], span[av-switch-icon], and span[av-switch-content] project content and have no inputs.
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | Switch size. |
disabled | boolean | false | Disables interaction. Also set by reactive forms. |
default-selected | boolean | false | Initial selected state for uncontrolled usage. |
selected | boolean | false | Selected state. Supports two-way binding with `[(selected)]`. |
selectedChange | EventEmitter<boolean> | — | Emits when the selected state changes. |
aria-label | string | — | Accessible label when no visible label is provided. |
name | string | — | Form field name for native form submission. |
value | string | 'on' | Form field value when selected in native form submission. |
SwitchGroup
Props for av-switch-group.
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | 'horizontal' | 'vertical' | 'vertical' | Layout direction of grouped switches. |