All components
Radiomark
The standalone circular radio indicator with a scaled center dot.
123456789101112131415import { Radiomark } from "#components/ui/base-ui/radiomark.tsx";import { indigoPalette } from "@stylexblocks/ui/themes/palettes.ts";
export function Example() { return ( <> {/* checked by default — pass checked={false} for the empty circle */} <Radiomark variant="solid" size="md" palette={indigoPalette} /> <Radiomark variant="outline" size="md" checked={false} />
{/* composition: the render prop replaces the underlying element */} <Radiomark variant="subtle" render={<li />} /> </> );}Variants
solid
subtle
outline
inverted
Sizes
xs
sm
md
lg
Install
1npx stylexblocks add radiomarkSource
components/ui/base-ui/radiomark.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222import * as stylex from "@stylexjs/stylex";import { mergeProps } from "@base-ui-components/react/merge-props";import { useRender } from "@base-ui-components/react/use-render";import type { StyleOverrideProps } from "#lib/recipe-props.ts";import { radiomarkDotVars } from '@stylexblocks/ui/recipes/radiomark.stylex.ts'
import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { colors } from '@stylexblocks/ui/tokens/colors.stylex.ts'import { cursor } from '@stylexblocks/ui/tokens/cursor.stylex.ts'import { radii } from '@stylexblocks/ui/tokens/radii.stylex.ts'import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'
const radiomarkStyles = stylex.create({ base: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, verticalAlign: 'top', color: colors.white, borderWidth: '1px', borderColor: { default: 'transparent', ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderDestructive, }, borderRadius: radii.full, cursor: { default: cursor.radio, ':is(:disabled, [disabled], [data-disabled])': cursor.disabled, }, outlineWidth: { default: null, ':focus-visible': '1px', }, outlineOffset: { default: null, ':focus-visible': '2px', }, outlineStyle: { default: null, ':focus-visible': 'solid', }, outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, }, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, [radiomarkDotVars.height]: '100%', [radiomarkDotVars.width]: '100%', [radiomarkDotVars.borderRadius]: radii.full, [radiomarkDotVars.backgroundColor]: 'currentColor', [radiomarkDotVars.scale]: 0.4, },})
const radiomarkVariants = stylex.create({ solid: { borderWidth: '1px', borderColor: { default: semanticColors.border, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderDestructive, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, backgroundColor: { default: null, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, color: { default: colors.white, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.contrast, }, }, subtle: { borderWidth: '1px', backgroundColor: colorPalette.muted, borderColor: { default: colorPalette.muted, ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderDestructive, }, color: { default: 'transparent', ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.fg, }, }, outline: { borderWidth: '1px', borderColor: { default: 'inherit', ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderDestructive, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, color: { default: colors.white, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.fg, }, [radiomarkDotVars.scale]: 0.6, }, inverted: { backgroundColor: semanticColors.bg, borderWidth: '1px', borderColor: { default: 'inherit', ':is([data-invalid], [aria-invalid="true"])': semanticColors.borderDestructive, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': 'currentcolor', }, color: { default: colors.white, ':is(:checked, [data-checked], [aria-checked="true"], [data-state="checked"])': colorPalette.solid, }, },})
const radiomarkSizes = stylex.create({ xs: { width: sizes._3, height: sizes._3, }, sm: { width: sizes._3_5, height: sizes._3_5, }, md: { width: sizes._4, height: sizes._4, }, lg: { width: sizes._5, height: sizes._5, },})
const radiomarkDot = stylex.create({ base: { height: radiomarkDotVars.height, width: radiomarkDotVars.width, borderRadius: radiomarkDotVars.borderRadius, backgroundColor: radiomarkDotVars.backgroundColor, scale: radiomarkDotVars.scale, },})
export type RadiomarkVariant = keyof typeof radiomarkVariants
export type RadiomarkSize = keyof typeof radiomarkSizes
// Checked styling restated locally — the generated recipe expresses checked// state as `:checked`, which only matches real form elements, so it is// applied conditionally from the `checked` prop instead.const radiomarkChecked = stylex.create({ solid: { backgroundColor: colorPalette.solid, color: colorPalette.contrast, borderColor: colorPalette.solid, }, subtle: { color: colorPalette.fg }, outline: { color: colorPalette.fg, borderColor: colorPalette.solid }, inverted: { color: colorPalette.solid, borderColor: "currentcolor" },});
export interface RadiomarkProps extends Omit<useRender.ComponentProps<"span">, "style">, StyleOverrideProps { variant?: RadiomarkVariant; size?: RadiomarkSize; checked?: boolean;}
/** * Radiomark — the standalone circular radio indicator with a scaled center * dot, composed with Base UI's useRender so it stays polymorphic via * `render`. */export function Radiomark({ render, variant = "solid", size = "md", checked = true, palette, style, children, ...props}: RadiomarkProps) { return useRender({ render, defaultTagName: "span", state: { variant, size, checked }, props: mergeProps<"span">( { "aria-hidden": true, ...stylex.props( palette, radiomarkStyles.base, radiomarkVariants[variant], radiomarkSizes[size], checked && radiomarkChecked[variant], style, ), children: checked ? (children ?? <span {...stylex.props(radiomarkDot.base)} />) : null, }, props, ), });}