BetaActions

CloseButton

The way out, on its own.

Overview

The way out, on its own.

First example

<CloseButton onPress={dismiss} accessibilityLabel="Fermer" />
<CloseButton variant="ghost" onPress={dismiss} accessibilityLabel="Fermer" />

Anatomy

<CloseButton onPress={dismiss} accessibilityLabel="Fermer le panneau" />

No slots. There is one node and one mark in it, and a component with a single child has nothing to publish a context for.

It is the affordance Chip, Alert, Dialog, Popover and BottomSheet already have, given a recipe of its own. Those five keep theirs: a close inside a component takes that component's colours and that component's scale, resolved once at its root (R5), and Chip.Close is five lines over the shared base for exactly that reason. What was missing is this one — a dismiss on something the library does not own: a card header, a banner, a sheet of your own.

Usage

The disc, and the bare cross

<CloseButton onPress={dismiss} accessibilityLabel="Fermer" />
<CloseButton variant="ghost" onPress={dismiss} accessibilityLabel="Fermer" />

secondary — the neutral disc — is the default, for the reason the Dialog gives at its own close: a cross floating on a panel with nothing under it reads as decoration, and the disc is what makes it a target. ghost is the bare cross, for a component that is already providing the target around it.

A mark of your own

<CloseButton accessibilityLabel="Fermer">
  <Icon as={XIcon} size={16} color={theme.colors.foreground} />
</CloseButton>

Children replace the built-in cross. Unset, the button draws its own from two bars a quarter turn apart, so this works in a project that has installed no icon set — the same bargain the Checkbox's tick and the Radio's dot take.

As another element

<CloseButton asChild onPress={dismiss}>
  <Link href="/">…</Link>
</CloseButton>

R12, through the base. The missing-label warning does not fire under asChild: the caller's element carries its own label or its own text.

Root props

CloseButtonProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
variantCloseButtonVariant | undefinedNo
sizeSize | undefinedNoThe box and the cross inside it. Square, so here it drives both.
radiusRadiusKey | undefinedNoOverrides the corner, which is a circle — `size` sets it to half the box. A squared close button in a squared panel is a real design, and every control here takes it.
colorstring | undefinedNoA raw tint (`'#7c3aed'`), never a token (R7). The disc, or the cross on `ghost`.
isDisabledboolean | undefinedNoDims the button and stops the press. R8: `disabled` is not part of the public vocabulary, `isX` is.
childrenReactNodeNoReplaces the built-in cross — an `Icon`, or any glyph. Unset, the button draws its own from two rotated bars, so a dismissible screen works in a project that has installed no icon set.
styleStyleProp<ViewStyle> | ((state: PressableStateCallbackType) => StyleProp<ViewStyle>)NoR9 — `Pressable`'s function form as much as an object or an array.

Slots

This component is standalone and exposes no public slot.

Variants, sizes and colour

Sizes

sizeBoxBarCross
xs2412~8
sm2814~10
md3216~11
lg4020~14

md is the reference implementation's close button measured — a 32-point square — and it is the Dialog's.

The bar is twice as long as the cross looks: a bar rotated a quarter turn spans length / √2 on each axis. It is kept as a ratio of the box rather than tabulated, so it is one cross at four sizes instead of four drawings of one — the Radio's dot, again.

The stroke does not scale. It is the shared thickness the Chip, the Alert and the Dialog all draw their cross at; a family of crosses that thicken with their container reads as four different marks rather than one at four sizes.

The box is small at xs, and it is still pressable: the target grows outwards through hitSlop, which costs nothing in layout, rather than the glyph growing.

radius is the circle size sets — half the box — and overridable, like everywhere else.

Variants and colour

variantDiscBorderCross
primaryaccentaccentForeground
secondarydefaultdefaultForeground
tertiaryborderforeground
ghostforeground

Four emphasis levels and no intent: dismissing is neither a success nor a danger. The close that carries an intent is the one inside a component that has one — Alert.Close in a danger alert — and it is the alert's recipe that says so, not a variant here.

secondary is the neutral grey, the Checkbox's and the Radio's reading of the word rather than the Button's accent-soft: a close button is grey before it is anything else, and it sits beside those two more often than beside a Button.

color is a raw value (R7), and it lands where the variant puts it: the disc on primary and secondary, the cross on tertiary and ghost.

There is no pressed colour. The press is the shared PressableFeedback treatment, because the base owns the press state — a cross has to be a different target from the panel around it — and a root that does not know it is pressed cannot resolve a colour for it. Every other close button in the library reads the same way.

Accessibility

  • accessibilityRole="button", overridable.
  • An accessibilityLabel is required, and its absence warns in development: a cross says "close" to someone who can see it and nothing at all to someone who cannot. Unlike an icon-only Button, the text beside it names the thing being dismissed rather than the action, so there is nothing to fall back on.
  • The warning does not fire under asChild, where the caller's element carries its own label.
  • The touch target is 8 points larger than the box on every side.

Migration from legacy

This component declares no rule of its own. The migration guide covers variants, colours and slots.

Implementation notes

The base, and the component

Two things share the name, and the split is the point:

WhereWhat it is
CloseButtonBase@xaui/native/systemThe behaviour: its own press state, the grown target, the missing-label warning, the built-in cross. Takes the styles a host recipe resolved.
CloseButton@xaui/native/close-buttonThe component: the base plus a recipe — a box that follows size, a disc that follows variant, and a color that tints it.

Writing a dismissible component of your own means reaching for the base and publishing close / closeGlyph from your recipe, exactly as Chip does. Dropping a dismiss into a layout means reaching for the component.

Alignment with the reference implementation

Identical: the 32-point box at the default size, the circle, the filled default rather than a bare glyph, the grown touch target, and children replacing the mark.

Three deltas:

TheirsOursWhy
A Button preset — tertiary, sm, isIconOnly, hitSlop={12}Its own recipe over the shared baseA preset inherits a label slot, a spinner and a fullWidth-shaped API for a control that has one mark and no text.
iconProps={{ size, color }}children, or color and sizeR1 — a prop that styles the inside of another component is the thing the v1 API exists to remove. The glyph is a child.
The cross is an imported iconTwo bars, a quarter turn apartA dismissible screen has to work in a project that has installed no icon set. Children still replace it.