HoverButton
A low-level button primitive that swaps its props between default, hover, and selected states.
HoverButton is an unthemed primitive built on Roblox's native ImageButton. You give it one set of ImageButton properties for each state (default, hover, and focus), and it renders whichever set matches the current hover or selected state. Clean UI uses it inside interactive controls such as Accordion headers and Tabs triggers. Use it when you need full control over a button's appearance. For a ready-styled button, use Button.
Import
import { HoverButton } from "@rbxts/react-clean-ui";Basic usage
default is required and always applies. hover and focus are optional and are layered on top of default, so each one only needs the properties that change in that state.
States
The active state is chosen in this order:
focuswhileisSelectedistrue, wherever the pointer is.hoverwhile the pointer is over the button.defaultalone otherwise.
The rendered props are { ...default, ...activeState }.
<HoverButton
isSelected={selected}
default={{
Size: UDim2.fromOffset(120, 40),
BackgroundColor3: Color3.fromHex("#F4F5F7"),
BackgroundTransparency: 0,
AutoButtonColor: false,
Event: {
Activated: () => setSelected(!selected),
},
}}
hover={{ BackgroundColor3: Color3.fromHex("#E1E4E8") }}
focus={{ BackgroundColor3: Color3.fromHex("#2E9D63") }}
/>Set AutoButtonColor: false in default. Otherwise Roblox's built-in hover and press tinting is applied on top of your own hover and focus styling.
Borders
Set borderThickness to draw a UIStroke around the button. A stroke is drawn only when the resolved thickness is greater than 0. With no border props anywhere, no stroke is rendered.
You can set the border props on HoverButton itself, inside default, hover, or focus, or in more than one place. Each field is resolved separately, and the state value wins over the top-level prop. So a hover set can change just the borderColor and keep the top-level thickness:
<HoverButton
borderThickness={2}
borderPosition="inner"
default={{
Size: UDim2.fromScale(1, 1),
BackgroundColor3: Color3.fromHex("#F4F5F7"),
BackgroundTransparency: 0,
AutoButtonColor: false,
borderColor: Color3.fromHex("#8A8F98"),
}}
hover={{ borderColor: Color3.fromHex("#4A7AC9") }}
focus={{
borderColor: Color3.fromHex("#2E9D63"),
borderThickness: 3,
}}
/>borderPosition defaults to "inner", which keeps the stroke inside the button's bounds. Use "outer" to draw it outside them.
Reacting to state in children
HoverButton gives its children a HoverButtonContext holding { hover: boolean; isSelected: boolean }. Nested content can read the button's current state from it without tracking hover itself.
import React, { useContext } from "@rbxts/react";
import { HoverButtonContext, Text } from "@rbxts/react-clean-ui";
function Label() {
const state = useContext(HoverButtonContext);
return <Text text={state?.hover ? "Hovered" : "Idle"} />;
}Press-down activation
HoverButton supports the same pressOn and onRelease props as Button. With pressOn="down", the active state's Event.Activated handler (or default's, if the active state has none) runs as soon as a mouse or touch press begins, and the native Activated for that press is swallowed. onRelease fires when that press ends or is cancelled, even if the pointer was dragged off the button.
<HoverButton
pressOn="down"
onRelease={() => setHeld(false)}
default={{
Size: UDim2.fromOffset(120, 40),
BackgroundColor3: Color3.fromHex("#F4F5F7"),
BackgroundTransparency: 0,
AutoButtonColor: false,
Event: {
Activated: () => setHeld(true),
},
}}
/>HoverButton has no disabled prop, so press-down is blocked instead while the merged state's Active is false. Gamepad and keyboard activation still go through the native Activated at the normal time and never get an onRelease.
Top-level events
HoverButton also takes an Event prop of its own, with the same shape as the ImageButton's Event table. It is merged with the Event of default and the active state set: when both define the same event, both handlers run, the state set's first, then the top-level one. HoverButton also forwards its ref to the rendered ImageButton.
Together, these let a HoverButton be the direct child of a Tooltip, with no Container wrapped around it:
<Tooltip content="Open the shop">
<HoverButton
default={{
Size: UDim2.fromOffset(120, 40),
BackgroundColor3: Color3.fromHex("#F4F5F7"),
BackgroundTransparency: 0,
AutoButtonColor: false,
}}
hover={{ BackgroundColor3: Color3.fromHex("#E1E4E8") }}
/>
</Tooltip>Props
HoverButton-specific props
| Prop | Type | Default | Description |
|---|---|---|---|
default | HoverButtonStateProps | required | Base ImageButton properties (plus border props), always applied. |
hover | HoverButtonStateProps | undefined | Properties layered over default while the pointer is over the button and it is not selected. |
focus | HoverButtonStateProps | undefined | Properties layered over default while isSelected is true. |
isSelected | boolean | false | Forces the focus state, whatever the hover state is. |
name | string | "HoverButton" | Name of the rendered ImageButton instance. |
pressOn | "down" | "up" | "up" | When a mouse/touch press activates the button: on release ("up") or as soon as it begins ("down"). |
onRelease | (button: ImageButton, input: InputObject) => void | undefined | With pressOn="down", called when a mouse/touch press that activated the button ends or is cancelled. |
Event | React.InstanceEvent<ImageButton> | undefined | Event handlers merged with the state sets' Event. On a shared event, the state set's handler runs first, then this one. |
children | React.ReactNode | undefined | Content rendered inside the button, after the border stroke. |
HoverButtonStateProps is React.InstanceProps<ImageButton> & BorderElementProps.
Border props
These are accepted on HoverButton itself and in each of default, hover, and focus. The active state's value wins over the top-level value.
| Prop | Type | Default | Description |
|---|---|---|---|
borderThickness | number | undefined | Stroke thickness in pixels. A UIStroke is only rendered when this is greater than 0. |
borderColor | Color3 | undefined | Stroke colour. When unset, the engine default (black) is used. |
borderTransparency | number | undefined | Stroke transparency. When unset, the engine default (0) is used. |
borderPosition | "inner" | "outer" | "inner" | "inner" draws the stroke inside the button's bounds; "outer" draws it outside them. |
Shared props
The button also supports props inherited from the following interfaces.
| Interface | Purpose |
|---|---|
BorderElementProps | Configures the optional UIStroke border, top-level or per state. |
Behaviour
- Only a fixed list of
ImageButtonproperties is forwarded from the state sets: layout and sizing, background and native border, image properties, selection and gamepad navigation,Modal,AutoButtonColor,Active,Visible,ZIndex,Event,Change, andTag. OtherImageButtonproperties are ignored even if they are set. BorderSizePixeldefaults to0when no state sets it.Eventhandlers are merged as{ ...default.Event, ...activeState.Event }.MouseEnterandMouseLeaveare always wrapped internally to track hover. The wrapper still calls the active state's handler, and also thedefaultstate's handler when the two differ.- The top-level
Eventprop is merged on top of the state sets' events. For an event both define, the state set's handler runs first, then the top-level handler. refis forwarded to the renderedImageButton.- Hover is tracked in local state through
MouseEnterandMouseLeave. - The border props are never forwarded to the
ImageButton. When drawn, the stroke is aUIStrokenamedStroke, rendered as the button's first child. - Without
pressOn="down", the event table is built exactly as described above. With it,Activatedskips mouse and touch input, and an internalInputBeganhandler runs the active state's ownInputBeganfirst, then activates the button unless the mergedActiveisfalse. - There is no
disabledstate. To show a disabled look, put it in your owndefault,hover, andfocussets, for example by settingActive: falseand matching colours.
Theme values
HoverButton is not themed and has no entry under theme.components. Its whole appearance comes from the default, hover, and focus prop sets and the border props.