Radio
<template>
<PeachyInput.Input>
<PeachyRadio.Input v-model:selected="selected">
<PeachyRadio.InputLabel>Radio group</PeachyRadio.InputLabel>
<div class="radio-wrapper">
<PeachyRadio.Radio value="1">
<template #after>
<PeachyRadio.Indicator />
<PeachyRadio.Label>Radio 1</PeachyRadio.Label>
</template>
</PeachyRadio.Radio>
</div>
<div class="radio-wrapper">
<PeachyRadio.Radio value="2">
<template #after>
<PeachyRadio.Indicator />
<PeachyRadio.Label>Radio 2</PeachyRadio.Label>
</template>
</PeachyRadio.Radio>
</div>
</PeachyRadio.Input>
</PeachyInput.Input>
</template>
.peachy-radio__input {
display: grid;
gap: var(--gap);
border: none;
}
.peachy-radio__input-label {
min-width: max-content;
padding-bottom: 0.5rem;
font-weight: var(--fw-bold);
}
.radio-wrapper {
display: flex;
align-items: center;
gap: var(--gap);
position: relative;
cursor: pointer;
width: fit-content;
padding: 0 1rem 0 0;
}
.peachy-radio__label {
cursor: pointer;
}
.peachy-radio:focus-visible {
outline: none !important;
}
.peachy-radio {
width: 1.25rem;
height: 1.25rem;
position: absolute;
}
.peachy-radio:has(:focus-visible) {
outline: 2px solid var(--brand-color);
outline-offset: 2px;
border-radius: 99rem;
}
.peachy-radio__indicator[data-checked="false"] {
--radio-color: var(--dimmed-text);
}
.peachy-radio__indicator[data-checked="true"] {
--radio-color: var(--brand-color-dimmed);
}
.peachy-radio__indicator:hover,
.peachy-radio:hover + .peachy-radio__indicator {
--radio-color: var(--text);
}
.peachy-radio__indicator[data-checked="true"]:hover,
.peachy-radio:hover + .peachy-radio__indicator[data-checked="true"],
.peachy-radio:focus + .peachy-radio__indicator {
--radio-color: var(--brand-color);
}
.peachy-radio__indicator {
transition: border 0.25s;
width: 1.25rem;
height: 1.25rem;
border: 2px solid var(--radio-color);
border-radius: 100%;
padding: 0.175rem;
position: relative;
}
.peachy-radio__indicator[data-checked="true"]::before {
content: "";
position: absolute;
inset: 0.15rem;
background-color: var(--radio-color);
border-radius: 100%;
transition:
background-color 0.25s,
transform 0.25s;
}
@media (prefers-reduced-motion: no-preference) {
.peachy-radio__indicator[data-checked="true"]::before {
animation: ScaleIn 0.25s;
}
}
/** ===== Animation ===== */
@keyframes ScaleIn {
from {
transform-origin: center;
transform: scale(0.6);
}
to {
transform: scale(1);
}
}
Anatomy
Radio does not use <PeachyInput.Label />
, but rather InputLabel
for the group, and Label
for individual radios.
<template>
<PeachyInput.Input>
<PeachyRadio.Input>
<template #before />
<PeachyRadio.InputLabel />
<PeachyRadio.Radio>
<PeachyRadio.Indicator />
<PeachyRadio.Label />
</PeachyRadio.Radio>
<template #after />
</PeachyRadio.Input>
</PeachyInput.Input>
</template>
<script lang="ts" setup>
import { PeachyRadio } from "typeach";
</script>
Indicator
has a slot, which only renders when the radio is checked.
Props & Emits
Input
Props
Name | Default | Type |
---|---|---|
selected | string? | |
disabled | false | boolean? |
readOnly | false | boolean? |
Emits
@ | Payload |
---|---|
update:selected | string |
validate | string |
clear-validation |
Radio
Props
Name | Default | Type |
---|---|---|
value | string | |
disabled | false | boolean? |
readOnly | false | boolean? |
Styling
INPUT
HAS DEFAULT STYLING
The Input
element has a style of appearance: none
, to allow styling it yourself. You should however style the input and Indicator
to be aligned, so that the click-area lines up (see the example).
CSS Selectors
Follows our CSS classes convention.
State selectors
Selector | Description | For |
---|---|---|
:checked | For a checked radio. |
|
:disabled | For a disabled radio. |
|
[data-readonly="<boolean>"] | For a read-only radio. |
|
[data-checked="<boolean>"] | For the checked state. |
|
Accessibility
Resources: One last time: custom styling radio buttons and checkboxes
READONLY
Native radio inputs do not offer a readonly state, and aria-readonly
has spotty support. So setting the radios to readonly makes their input disabled with aria-disabled
to keeps it in the tab order, and manually prevents the input from being edited.
There is some disagreements if disabled
conveys the right intent as a replacement for readonly, however, usually the solution people arrive at when they want a readonly
radio is to make it disabled - so I figure this is a reasonable compromise.
References: Why is aria-readonly allowed for checkboxes and radios?, Provide a way to have disabled form controls to be submitted (was: readonly attribute)
Keyboard interactions
Key | Action |
---|---|
↑ ← | Moves focus to the previous radio and selects it. |
↓ → | Moves focus to the next radio and selects it. |