All components
Input Addon
Prefix and suffix segments that share height and variant styling with an adjacent input.
https://
12345678910111213import { InputGroup } from "#components/ui/base-ui/input-addon.tsx";
export function Example() { return ( // variant/size resolve once on Root; parts inherit them via context and // expose data-variant / data-size, joining seamlessly on either side <InputGroup.Root variant="outline" size="md"> <InputGroup.Addon>https://</InputGroup.Addon> <InputGroup.Input placeholder="yoursite.com" /> <InputGroup.Addon>.dev</InputGroup.Addon> </InputGroup.Root> );}Variants
https://
outlinehttps://
subtlehttps://
flushedSizes
https://
xshttps://
smhttps://
mdhttps://
lghttps://
xlInstall
1npx stylexblocks add input-addonSource
components/ui/base-ui/input-addon.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166import * as stylex from "@stylexjs/stylex";import { createSlotRecipeContext } from "#lib/base-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe } from "#lib/recipe-props.ts";import { inputSizes, inputStyles, inputTextStyles, inputVariants,} from "@stylexblocks/ui/recipes/input.ts";import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { semanticRadii } from '@stylexblocks/ui/semantic-tokens/radii.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'import { textStyles } from '@stylexblocks/ui/text-styles.ts'
const inputAddonStyles = stylex.create({ base: { flex: '0 0 auto', width: 'auto', height: 'var(--input-height)', display: 'flex', alignItems: 'center', whiteSpace: 'nowrap', },})
const inputAddonSizes = stylex.create({ xs: { borderRadius: semanticRadii.control, paddingInline: spacing._2, '--input-height': sizes.controlXs, }, sm: { borderRadius: semanticRadii.control, paddingInline: spacing._2_5, '--input-height': sizes.controlSm, }, md: { borderRadius: semanticRadii.control, paddingInline: spacing._3, '--input-height': sizes.controlMd, }, lg: { borderRadius: semanticRadii.control, paddingInline: spacing._4_5, '--input-height': sizes.controlLg, }, xl: { borderRadius: semanticRadii.control, paddingInline: spacing._6, '--input-height': sizes.controlXl, },})
const inputAddonVariants = stylex.create({ outline: { border: '1px solid', borderColor: semanticColors.border, backgroundColor: semanticColors.bgMuted, }, subtle: { border: '2px solid', borderColor: 'transparent', backgroundColor: semanticColors.bgMuted, }, flushed: { borderBottom: '1px solid', borderColor: 'inherit', borderRadius: 0, paddingInline: 0, backgroundColor: 'transparent', },})
const inputAddonTextStyles = { xs: textStyles.xs, sm: textStyles.sm, md: textStyles.sm, lg: textStyles.md, xl: textStyles.md,} as const
export type InputAddonSize = keyof typeof inputAddonSizes
export type InputAddonVariant = keyof typeof inputAddonVariants
const inputGroupRoot = stylex.create({ base: { display: "flex", alignItems: "stretch", width: "100%" },});
// joins addons and field into one control: outer corners keep the control// radius via :first-child/:last-child, inner edges drop radius and borderconst inputGroupJoin = stylex.create({ addon: { // the recipe's `border: '1px solid'` shorthand is a Chakra-ism StyleX // drops; restate the box borders explicitly borderStyle: "solid", borderBlockWidth: "1px", borderStartStartRadius: { default: 0, ":first-child": semanticRadii.control }, borderEndStartRadius: { default: 0, ":first-child": semanticRadii.control }, borderStartEndRadius: { default: 0, ":last-child": semanticRadii.control }, borderEndEndRadius: { default: 0, ":last-child": semanticRadii.control }, borderInlineStartWidth: { default: 0, ":first-child": "1px" }, borderInlineEndWidth: { default: 0, ":last-child": "1px" }, }, field: { flexGrow: 1, flexShrink: 1, minWidth: 0, width: "auto", borderStartStartRadius: { default: 0, ":first-child": semanticRadii.control }, borderEndStartRadius: { default: 0, ":first-child": semanticRadii.control }, borderStartEndRadius: { default: 0, ":last-child": semanticRadii.control }, borderEndEndRadius: { default: 0, ":last-child": semanticRadii.control }, },});
// defineSlotRecipe validates the shape at compile time: default values must// exist in the axis maps, and slot keys must be `styles` or a declared axisconst inputGroupSlotRecipe = defineSlotRecipe({ slots: { root: { styles: inputGroupRoot }, addon: { styles: inputAddonStyles, sizes: inputAddonSizes, variants: inputAddonVariants, }, input: { styles: inputStyles, sizes: inputSizes, variants: inputVariants }, }, defaultVariants: { variant: "outline", size: "md" },});
/** * InputGroup — composes the input and input-addon recipes with a shared * variant context: `<InputGroup.Root variant size>` resolves once, and both * Addon and Input inherit it. Addons join seamlessly on either side of the * field. Composition per part via `render`. */const { withProvider, withContext } = createSlotRecipeContext(inputGroupSlotRecipe);
export const InputGroup = { Root: withProvider("div", "root", "InputGroupRoot"), Addon: withContext( "span", (v) => [ inputAddonStyles.base, inputAddonTextStyles[v.size], inputAddonSizes[v.size], inputAddonVariants[v.variant], v.variant !== "flushed" && inputGroupJoin.addon, ], "InputGroupAddon", ), Input: withContext( "input", (v) => [ inputStyles.base, inputTextStyles[v.size], inputSizes[v.size], inputVariants[v.variant], v.variant !== "flushed" && inputGroupJoin.field, ], "InputGroupInput", ),};