diff --git a/packages/api-generator/src/locale/en/VSwitch.json b/packages/api-generator/src/locale/en/VSwitch.json
index b9de3b611e4..231f8d14138 100644
--- a/packages/api-generator/src/locale/en/VSwitch.json
+++ b/packages/api-generator/src/locale/en/VSwitch.json
@@ -3,9 +3,10 @@
"props": {
"flat": "Display component without elevation. Default elevation for thumb is 4dp, `flat` resets it.",
"indeterminate": "Sets an indeterminate state for the switch.",
- "inset": "Controls the track and thumb styling\n- **tonal** (or `true`) enlarges the track to encompass the thumb\n- **material** applies the Material Design 3 treatment: an outlined track that fills when on and a thumb that morphs between states\n- **square** is the **material** variant with less round corners.",
+ "inset": "Controls the track and thumb styling\n- **tonal** (or `true`) enlarges the track to encompass the thumb\n- **material** applies the Material Design 3 treatment: an outlined track that fills when on and a thumb that morphs between states\n- **square** is the **material** variant with less round corners.\n\nNon-boolean values were introduced in v4.1.0.",
"loading": "Displays circular progress bar. Can either be a String which specifies which color is applied to the progress bar (any material color or theme color - primary, secondary, success, info, warning, error) or a Boolean which uses the component color (set by color prop - if it's supported by the component) or the primary color.",
- "multiple": "Changes expected model to an array."
+ "multiple": "Changes expected model to an array.",
+ "size": "Scales the track and thumb. Accepts the predefined sizes **x-small**, **small**, **default**, **large**, and **x-large**, or a numeric value for a custom scale."
},
"events": {
"update:indeterminate": "Event that is emitted when the component's indeterminate state changes."
diff --git a/packages/docs/src/data/new-in.json b/packages/docs/src/data/new-in.json
index 63a04208188..aabca09c81a 100644
--- a/packages/docs/src/data/new-in.json
+++ b/packages/docs/src/data/new-in.json
@@ -526,6 +526,10 @@
}
},
"VSwitch": {
+ "props": {
+ "size": "4.1.0",
+ "thumbColor": "4.1.0"
+ },
"slots": {
"thumb": "3.5.0",
"track-false": "3.5.0",
diff --git a/packages/docs/src/examples/v-switch/prop-colors.vue b/packages/docs/src/examples/v-switch/prop-colors.vue
index cb2dfe5f201..a25a199bb96 100644
--- a/packages/docs/src/examples/v-switch/prop-colors.vue
+++ b/packages/docs/src/examples/v-switch/prop-colors.vue
@@ -1,129 +1,47 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Default
+ Inset
+ Material
+ Square
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
@@ -133,15 +51,54 @@
diff --git a/packages/docs/src/examples/v-switch/prop-flat.vue b/packages/docs/src/examples/v-switch/prop-flat.vue
deleted file mode 100644
index 661d91a1656..00000000000
--- a/packages/docs/src/examples/v-switch/prop-flat.vue
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
diff --git a/packages/docs/src/examples/v-switch/prop-inset.vue b/packages/docs/src/examples/v-switch/prop-inset.vue
deleted file mode 100644
index 78e3e43413d..00000000000
--- a/packages/docs/src/examples/v-switch/prop-inset.vue
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
- {
- "figma": "https://www.figma.com/design/5f4g4pbbBsk9TTWX4Xvlx1/PRO-v3.0---Official-Vuetify-3-UI-Kit?node-id=2723-45692&t=tC3y53U3XKPv8ZyJ-4"
- }
-
diff --git a/packages/docs/src/examples/v-switch/prop-states.vue b/packages/docs/src/examples/v-switch/prop-states.vue
index 0e75326f1b5..a18c11eb749 100644
--- a/packages/docs/src/examples/v-switch/prop-states.vue
+++ b/packages/docs/src/examples/v-switch/prop-states.vue
@@ -1,8 +1,29 @@
+
+
+ Default
+ Inset
+ Material
+ Square
+
+
+
+
+
+
+
+
+
diff --git a/packages/docs/src/examples/v-switch/usage.vue b/packages/docs/src/examples/v-switch/usage.vue
index 9f99a872006..af8179ea8c7 100644
--- a/packages/docs/src/examples/v-switch/usage.vue
+++ b/packages/docs/src/examples/v-switch/usage.vue
@@ -5,12 +5,78 @@
:name="name"
:options="options"
>
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -19,12 +85,40 @@
const name = 'v-switch'
const model = ref('default')
const indeterminate = ref(false)
- const options = ['inset']
+ const icons = ref(true)
+ const loading = ref(false)
+ const options = ['inset', 'material', 'square']
+
+ const colors = ['primary', '#ac46ff', 'orange-darken-2']
+ const color = ref()
+ const baseColor = ref()
+ const thumbColor = ref()
+
+ const sizes = ['x-small', 'small', 'default', 'large', 'x-large']
+ const sizeIndex = ref(2)
+ const sizeTicks = { 0: 'xs', 1: 'sm', 2: 'md', 3: 'lg', 4: 'xl' }
+ const size = computed(() => sizes[sizeIndex.value])
+
+ const requiresLatest = computed(() => {
+ return size.value !== 'default' ||
+ ['material', 'square'].includes(model.value) ||
+ !!thumbColor.value
+ })
+
const props = computed(() => {
return {
label: 'Switch',
- inset: model.value === 'inset' || undefined,
+ inset: model.value === 'default' ? undefined
+ : model.value === 'inset' ? true
+ : model.value,
+ size: size.value === 'default' ? undefined : size.value,
+ 'true-icon': icons.value ? 'mdi-check' : undefined,
+ 'false-icon': icons.value ? 'mdi-close' : undefined,
+ color: color.value || undefined,
+ 'base-color': baseColor.value || undefined,
+ 'thumb-color': thumbColor.value || undefined,
indeterminate: indeterminate.value || undefined,
+ loading: loading.value || undefined,
}
})
diff --git a/packages/docs/src/pages/en/components/switches.md b/packages/docs/src/pages/en/components/switches.md
index dff5bc8b185..8f7974e2743 100644
--- a/packages/docs/src/pages/en/components/switches.md
+++ b/packages/docs/src/pages/en/components/switches.md
@@ -47,18 +47,6 @@ Switches can be colored by using any of the builtin colors and contextual names
-
-
-#### Inset
-
-You can make switch render in inset mode.
-
-
-
#### Model as array
Multiple `v-switch`'s can share the same **v-model** by using an array.
diff --git a/packages/vuetify/src/components/VSwitch/VSwitch.sass b/packages/vuetify/src/components/VSwitch/VSwitch.sass
index 4286b5c1f87..ca7b2e13552 100644
--- a/packages/vuetify/src/components/VSwitch/VSwitch.sass
+++ b/packages/vuetify/src/components/VSwitch/VSwitch.sass
@@ -1,3 +1,5 @@
+@use 'sass:list'
+@use 'sass:math'
@use 'sass:selector'
@use '../../styles/settings'
@use '../../styles/tools'
@@ -5,9 +7,25 @@
@include tools.layer('components')
.v-switch
+ --v-switch-scale: 1
+
.v-label
padding-inline-start: $switch-label-margin-inline-start
+ @each $name, $size in $switch-sizes
+ .v-switch--size-#{$name}
+ --v-switch-scale: #{math.div(list.nth($size, 1), $switch-track-height)}
+ --v-switch-track-height: #{list.nth($size, 1)}
+ --v-switch-thumb-height: #{list.nth($size, 2)}
+ --v-switch-thumb-width: #{list.nth($size, 3)}
+
+ @each $name, $size in $switch-inset-sizes
+ .v-switch--inset.v-switch--size-#{$name}
+ --v-switch-scale: #{math.div(list.nth($size, 1), $switch-inset-track-height)}
+ --v-switch-track-height: #{list.nth($size, 1)}
+ --v-switch-thumb-height: #{list.nth($size, 2)}
+ --v-switch-thumb-width: #{list.nth($size, 3)}
+
.v-switch__loader
display: flex
@@ -41,17 +59,16 @@
padding: 0 5px
background-color: $switch-track-background
border-radius: $switch-track-radius
- height: $switch-track-height
+ height: var(--v-switch-track-height)
opacity: $switch-track-opacity
- min-width: $switch-track-width
+ min-width: calc(#{$switch-track-width} * var(--v-switch-scale))
cursor: pointer
transition: $switch-track-transition
.v-switch--inset &
border-radius: $switch-inset-track-border-radius
font-size: .75rem
- height: $switch-inset-track-height
- min-width: $switch-inset-track-width
+ min-width: calc(#{$switch-inset-track-width} * var(--v-switch-scale))
.v-switch__thumb
align-items: center
@@ -60,9 +77,9 @@
border-radius: $switch-thumb-radius
display: flex
font-size: .75rem
- height: $switch-thumb-height
+ height: var(--v-switch-thumb-height)
justify-content: center
- width: $switch-thumb-width
+ width: var(--v-switch-thumb-width)
pointer-events: none
transition: $switch-thumb-transition
position: relative
@@ -78,8 +95,6 @@
@include tools.elevation(0)
.v-switch--inset &
- height: $switch-inset-thumb-height
- width: $switch-inset-thumb-width
transform: scale(var(--v-switch-inset-thumb-off-scale, #{$switch-inset-thumb-off-scale}))
&--filled
@@ -175,9 +190,9 @@
width: calc(var(--v-switch-thumb-height) * 1.666666667)
@include tools.ltr()
- transform: translateX(-$switch-thumb-transform)
+ transform: translateX(calc(#{-$switch-thumb-transform} * var(--v-switch-scale)))
@include tools.rtl()
- transform: translateX($switch-thumb-transform)
+ transform: translateX(calc(#{$switch-thumb-transform} * var(--v-switch-scale)))
.v-icon
position: absolute
@@ -188,9 +203,9 @@
.v-selection-control--dirty
.v-selection-control__input
@include tools.ltr()
- transform: translateX($switch-thumb-transform)
+ transform: translateX(calc(#{$switch-thumb-transform} * var(--v-switch-scale)))
@include tools.rtl()
- transform: translateX(-$switch-thumb-transform)
+ transform: translateX(calc(#{-$switch-thumb-transform} * var(--v-switch-scale)))
&.v-switch--indeterminate
.v-selection-control__input
diff --git a/packages/vuetify/src/components/VSwitch/VSwitch.tsx b/packages/vuetify/src/components/VSwitch/VSwitch.tsx
index 5df664a984d..e53187b6128 100644
--- a/packages/vuetify/src/components/VSwitch/VSwitch.tsx
+++ b/packages/vuetify/src/components/VSwitch/VSwitch.tsx
@@ -15,6 +15,7 @@ import { useFocus } from '@/composables/focus'
import { forwardRefs } from '@/composables/forwardRefs'
import { LoaderSlot, useLoader } from '@/composables/loader'
import { useProxiedModel } from '@/composables/proxiedModel'
+import { makeSizeProps } from '@/composables/size'
// Utilities
import { ref, toRef, useId } from 'vue'
@@ -57,8 +58,18 @@ export const makeVSwitchProps = propsFactory({
...omit(makeVInputProps(), ['glow']),
...makeVSelectionControlProps(),
+ ...makeSizeProps(),
}, 'VSwitch')
+const predefinedSizes = ['x-small', 'small', 'default', 'large', 'x-large']
+const iconSizes: Record
= {
+ 'x-small': 11,
+ small: 14,
+ default: 16,
+ large: 18,
+ 'x-large': 22,
+}
+
export const VSwitch = genericComponent(
props: {
modelValue?: T | null
@@ -100,6 +111,11 @@ export const VSwitch = genericComponent(
const uid = useId()
const id = toRef(() => props.id || `switch-${uid}`)
+ const isPredefinedSize = toRef(() => predefinedSizes.includes(props.size as string))
+ const iconSize = toRef(() => {
+ return isPredefinedSize.value ? iconSizes[props.size as string] : Math.round(16 * Number(props.size) / 32)
+ })
+
function onChange () {
if (indeterminate.value) {
indeterminate.value = false
@@ -128,6 +144,7 @@ export const VSwitch = genericComponent(
{ 'v-switch--inset-material': isMaterial },
{ 'v-switch--inset-square': props.inset === 'square' },
{ 'v-switch--indeterminate': indeterminate.value },
+ isPredefinedSize.value ? `v-switch--size-${props.size}` : undefined,
loaderClasses.value,
props.class,
]}
@@ -136,7 +153,10 @@ export const VSwitch = genericComponent(
v-model={ model.value }
id={ id.value }
focused={ isFocused.value }
- style={ props.style }
+ style={[
+ { '--v-switch-scale': isPredefinedSize.value ? undefined : Number(props.size) / 32 },
+ props.style,
+ ]}
>
{{
...slots,
@@ -228,7 +248,7 @@ export const VSwitch = genericComponent(
defaults={{
VIcon: {
icon,
- size: isMaterial ? 16 : 'x-small',
+ size: isMaterial ? iconSize.value : 'x-small',
},
}}
>
@@ -243,7 +263,7 @@ export const VSwitch = genericComponent(
class={ isMaterial ? textColorClasses.value : undefined }
style={ isMaterial ? textColorStyles.value : undefined }
icon={ icon }
- size={ isMaterial ? 16 : 'x-small' }
+ size={ isMaterial ? iconSize.value : 'x-small' }
/>
))) : (
(
active={ slotProps.isActive }
color={ slotProps.color }
indeterminate
- size="16"
+ size={ iconSize.value }
width="2"
/>
)
diff --git a/packages/vuetify/src/components/VSwitch/__tests__/VSwitch.spec.browser.tsx b/packages/vuetify/src/components/VSwitch/__tests__/VSwitch.spec.browser.tsx
index c60d75ca635..0d20f01aedc 100644
--- a/packages/vuetify/src/components/VSwitch/__tests__/VSwitch.spec.browser.tsx
+++ b/packages/vuetify/src/components/VSwitch/__tests__/VSwitch.spec.browser.tsx
@@ -5,6 +5,9 @@ import { gridOn, showcase } from '@test'
const contextColor = 'rgb(0, 0, 255)'
const color = 'rgb(255, 0, 0)'
+const thumbColor = 'rgb(0, 255, 0)'
+const sizes = ['x-small', 'small', 'default', 'large', 'x-large'] as const
+
const stories = {
'Explicit color': gridOn([undefined], [true, false], (_, active) => (
@@ -19,10 +22,30 @@ const stories = {
'No color': gridOn([undefined], [true, false], (_, active) => (
)),
+ 'Inset tonal': gridOn([color, undefined], [true, false], (color, active) => (
+
+ )),
+ 'Inset material': gridOn([color, undefined], [true, false], (color, active) => (
+
+ )),
+ 'Inset square': gridOn([color, undefined], [true, false], (color, active) => (
+
+ )),
+ 'Icons (no color)': gridOn([false, 'material'] as const, [true, false], (inset, active) => (
+
+ )),
+ 'Icons (color)': gridOn([false, 'tonal', 'material'] as const, [true, false], (inset, active) => (
+
+ )),
+ 'Thumb color': gridOn([false, 'tonal', 'material'] as const, [true, false], (inset, active) => (
+
+ )),
+ Sizes: gridOn(sizes, [true, false], (size, active) => (
+
+ )),
}
const props = {
loading: [true],
- inset: [true],
indeterminate: [true],
}
diff --git a/packages/vuetify/src/components/VSwitch/_variables.scss b/packages/vuetify/src/components/VSwitch/_variables.scss
index a8a0eca8aa9..6436f271e09 100644
--- a/packages/vuetify/src/components/VSwitch/_variables.scss
+++ b/packages/vuetify/src/components/VSwitch/_variables.scss
@@ -59,3 +59,19 @@ $switch-track-width: 36px !default;
$switch-track-height: 14px !default;
$switch-track-opacity: .6 !default;
$switch-track-transition: .2s background-color settings.$standard-easing !default;
+
+$switch-sizes: (
+ 'x-small': ($switch-track-height - 5px, 13px, 13px),
+ 'small': ($switch-track-height - 3px, 16px, 16px),
+ 'default': ($switch-track-height, $switch-thumb-height, $switch-thumb-width),
+ 'large': ($switch-track-height + 3px, 24px, 24px),
+ 'x-large': ($switch-track-height + 5px, 28px, 28px),
+) !default;
+
+$switch-inset-sizes: (
+ 'x-small': ($switch-inset-track-height - 12px, 15px, 15px),
+ 'small': ($switch-inset-track-height - 6px, 20px, 20px),
+ 'default': ($switch-inset-track-height, $switch-inset-thumb-height, $switch-inset-thumb-width),
+ 'large': ($switch-inset-track-height + 6px, 30px, 30px),
+ 'x-large': ($switch-inset-track-height + 12px, 34px, 34px),
+) !default;
\ No newline at end of file