Button

Import
import { Button } from "reshaped";
import type { ButtonProps } from "reshaped";
Related components
7 colors, 4 sizes and 4 variants
Automatically resolves to correct HTML tag
Correctly handles keyboard navigation
Supports button groups
Supports responsive size values


Button accepts children property to render its content. It can be any content from text to a group of elements stacked together inside the Button.

<Button onClick={() => {}}>About us</Button>

Button works with both href and onClick properties. Using either of them will resolve Button rendering to <a> or <button> tag respectively.

If you use Button for client-side routing, make sure to pass href property to it together with onClick. That will render Button using a correct HTML tag keeping native behavior working like expected. For instance, users can open navigation links using Button component in a new tab.

<View gap={3} direction="row">
  <Button href="/" attributes={{ target: "_blank" }}>
    Back to main page
  </Button>

  <Button onClick={() => {}}>Sign in</Button>
</View>

Button supports multiple variants depending on the context they are used in.

<View gap={3} direction="row" align="center">
  <Button onClick={() => {}}>Solid button</Button>
  <Button variant="faded" onClick={() => {}}>
    Faded button
  </Button>
  <Button variant="outline" onClick={() => {}}>
    Outline button
  </Button>
  <Button variant="ghost" onClick={() => {}}>
    Ghost button
  </Button>
</View>

Button uses neutral color by default but supports other color values with a color property. That way, you can emphasize your main call-to-action button using a primary button, draw user attention to a critical action or highlight a final step of the procedure with positive color.

<View gap={3}>
  <View gap={3} direction="row" align="center">
    <Button color="primary" onClick={() => {}}>
      Solid button
    </Button>
    <Button color="primary" variant="faded" onClick={() => {}}>
      Faded button
    </Button>
    <Button color="primary" variant="outline" onClick={() => {}}>
      Outline button
    </Button>
    <Button color="primary" variant="ghost" onClick={() => {}}>
      Ghost button
    </Button>
  </View>

  <View gap={3} direction="row" align="center">
    <Button color="critical" onClick={() => {}}>
      Solid button
    </Button>
    <Button color="critical" variant="faded" onClick={() => {}}>
      Faded button
    </Button>
    <Button color="critical" variant="outline" onClick={() => {}}>
      Outline button
    </Button>
    <Button color="critical" variant="ghost" onClick={() => {}}>
      Ghost button
    </Button>
  </View>

  <View gap={3} direction="row" align="center">
    <Button color="positive" onClick={() => {}}>
      Solid button
    </Button>
    <Button color="positive" variant="faded" onClick={() => {}}>
      Faded button
    </Button>
    <Button color="positive" variant="outline" onClick={() => {}}>
      Outline button
    </Button>
    <Button color="positive" variant="ghost" onClick={() => {}}>
      Ghost button
    </Button>
  </View>
</View>

You can use either white or black button color when you need to display a button over the media content. These values use static color tokens, which means they look the same in light and dark mode. That's important for media content since images and videos preserve their colors in both color modes.

<View gap={3} direction="row">
  <View.Item columns={6}>
    <View borderRadius="medium" overflow="hidden" aspectRatio={16 / 9}>
      <Image src="/img/examples/image-retina.webp" />
      <div style={{ position: "absolute", top: 16, left: 16 }}>
        <Button
          color="black"
          variant="faded"
          icon={IconHeart}
          onClick={() => {}}
        />
      </div>
    </View>
  </View.Item>
  <View.Item columns={6}>
    <View borderRadius="medium" overflow="hidden">
      <Overlay
        position="top"
        backgroundSlot={
          <View aspectRatio={16 / 9}>
            <Image src="/img/examples/image-retina.webp" />
          </View>
        }
      >
        <Button color="white" icon={IconZap} onClick={() => {}} />
      </Overlay>
    </View>
  </View.Item>
</View>

When rendering Button on top of a dynamic color, you can use inherit color to automatically use the current text color for the Button styles. This could be helpful when you're rendering it on primary background that changes based on the theme or your component supports multiple background colors while you want to use the same Button component in it. In the following example, we're using inherit color on top of primary and neutral colors, so both Buttons stay white in dark mode, but one of them turns black in light mode.

<View gap={3} direction="row">
  <View padding={4} backgroundColor="primary" borderRadius="small">
    <Button color="inherit" variant="ghost">
      Inherit
    </Button>
  </View>
  <View padding={4} backgroundColor="neutral" borderRadius="small">
    <Button color="inherit" variant="ghost">
      Inherit
    </Button>
  </View>
</View>

Button comes in 4 sizes, with the medium size used by default. small size can be used in cases when there is limited space available for rendering, like in complex tools or dashboards. large and xlarge are built for marketing pages.

<View gap={3} direction="row">
  <Button size="small" onClick={() => {}}>
    Save progress
  </Button>
  <Button size="medium" onClick={() => {}}>
    Sign in
  </Button>
  <Button size="large" onClick={() => {}}>
    Get started
  </Button>
  <Button size="xlarge" onClick={() => {}}>
    Subscribe
  </Button>
</View>

Button supports responsive syntax for its size property, which means you can change its size based on the viewport.

<Button size={{ s: "medium", m: "large" }} onClick={() => {}}>
  Get started
</Button>

The content inside the Button automatically defines its width. If you want to stretch Button to the entire width of its parent element, you can use the fullWidth property. For example, you can use this property on mobile viewports where most of the buttons usually take the entire width of the page.

<Button fullWidth color="primary" onClick={() => {}}>
  Download application
</Button>

Button supports responsive syntax for its fullWidth property, which means you can change its width based on the viewport.

<Button fullWidth={{ s: true, m: false }} onClick={() => {}}>
  Get started
</Button>

If button is used for asynchronous actions, use the loading property to give feedback to the user. Turning the loading state on will prevent the Button click events without disabling it visually.

<View gap={3} direction="row">
  <Button loading onClick={() => {}}>
    Sign in
  </Button>
  <Button color="critical" loading onClick={() => {}}>
    Delete message
  </Button>
  <Button variant="ghost" loading onClick={() => {}}>
    Load more
  </Button>
</View>

Buttons used for actions can also be disabled from the user input with a disabled flag. That will completely prevent user from interacting with the Button.

<Button disabled onClick={() => {}}>
  Sign in
</Button>

Icon can be rendered on either side of the Button text content with icon and endIcon properties, automatically wrapping SVG you pass to it with an Icon utility.

<View gap={3} direction="row">
  <Button icon={IconZap} onClick={() => {}}>
    1-click order
  </Button>
  <Button endIcon={IconZap} onClick={() => {}}>
    1-click order
  </Button>
</View>

If there is not enough space to display a text label, Button can be rendered with just an icon. In this case, make sure also to pass aria-label attribute to the Button or wrap it with Tooltip component to keep it accessible for screen readers.

<View gap={3} direction="row">
  <Button
    icon={IconZap}
    attributes={{ "aria-label": "1-click order" }}
    onClick={() => {}}
  />
  <Button
    icon={IconZap}
    variant="ghost"
    attributes={{ "aria-label": "1-click order" }}
    onClick={() => {}}
  />
</View>

If you want to differentiate specific buttons in the product or turn them into circular Buttons, you can use a rounded flag for both buttons with text and only icons.

<View gap={3} direction="row">
  <Button rounded onClick={() => {}}>
    Edit profile
  </Button>
  <Button
    rounded
    icon={IconZap}
    variant="faded"
    color="primary"
    attributes={{ "aria-label": "1-click order" }}
    onClick={() => {}}
  />
  <Button
    rounded
    icon={IconZap}
    variant="outline"
    attributes={{ "aria-label": "1-click order" }}
    onClick={() => {}}
  />
  <Button
    rounded
    icon={IconZap}
    variant="ghost"
    attributes={{ "aria-label": "1-click order" }}
    onClick={() => {}}
  />
</View>

You could use Button elevated property when you want to add a shadow to it. For example, it can be used for rendering Buttons on top of maps or any decorative backgrounds. Note that elevated property is not supported for the ghost variant.

<View gap={3} direction="row">
  <Button variant="faded" elevated icon={IconZap} onClick={() => {}}>
    Elevated outline
  </Button>
  <Button color="white" elevated icon={IconZap} onClick={() => {}}>
    Elevated white
  </Button>
</View>

You can group multiple buttons together using the Button.Group compound component. Each individual button inside still works with all supported properties.

<View gap={4} align="start">
  <Button.Group>
    <Button variant="faded">File</Button>
    <Button variant="faded">Edit</Button>
    <Button variant="faded">View</Button>
  </Button.Group>

  <Button.Group>
    <Button>Save changes</Button>
    <DropdownMenu>
      <DropdownMenu.Trigger>
        {(attributes) => <Button icon={IconDown} attributes={attributes} />}
      </DropdownMenu.Trigger>
      <DropdownMenu.Content>
        <DropdownMenu.Item>Copy link</DropdownMenu.Item>
        <DropdownMenu.Item>Update permissions</DropdownMenu.Item>
        <DropdownMenu.Item>Save to folder</DropdownMenu.Item>
      </DropdownMenu.Content>
    </DropdownMenu>
  </Button.Group>
</View>

As you may have noticed, we're using a View component in the examples to group the buttons. Using ghost buttons may cause misalignment with the content since its background is not visible.

In this case, you can use Button.Aligner utility which will adjust the space automatically. Passing a position property to it will define which sides of the Button need to be aligned with the content. It supports both, string and array values.

<View gap={3}>
  React components that make your product shine.
  <Button.Aligner position={["start", "top"]}>
    <Button variant="ghost" color="primary" onClick={() => {}}>
      Learn more
    </Button>
  </Button.Aligner>
</View>

You can use this approach for adding icon-only buttons to your components without having them take extra space. We're also not passing the position value to the Aligner component, so it applies alignment on all sides.

<Card>
  <View gap={3} direction="row">
    <View.Item grow>Content</View.Item>
    <Button.Aligner>
      <Button
        icon={IconZap}
        variant="ghost"
        attributes={{ "aria-label": "1-click order" }}
        onClick={() => {}}
      />
    </Button.Aligner>
  </View>
</Card>

When nesting Button component inside another component rendering a button tag, you can use the as property to render it as another html element, like span. When used, Button will automatically add correct role and tabIndex to it, keeping the markup valid while still making it interactive. In this case, it's also a good idea to add event.stopPropagation() to the inner button if you want to prevent click events bubbling and triggering onClick of its parent component.

<MenuItem
  onClick={() => {}}
  endSlot={
    <Button
      variant="ghost"
      size="small"
      icon={IconZap}
      as="span"
      onClick={(e) => {
        e.stopPropagation();
      }}
    />
  }
>
  Outer button
</MenuItem>

With the help of responsive properties, you have a lot of flexibility in terms of how Buttons should behave on different viewports. Its size and fullWidth property can receive values using the responsive property syntax, accepting different values for specific viewports. For example, you can completely change their layout on mobile:

<View direction={{ s: "column", m: "row" }} justify="end" gap={2}>
  <Button
    size={{ s: "large", m: "medium" }}
    color="primary"
    fullWidth={{ s: true, m: false }}
  >
    Confirm
  </Button>
  <Button size={{ s: "large", m: "medium" }} fullWidth={{ s: true, m: false }}>
    Cancel
  </Button>
</View>
  • Make sure to pass a meaningful aria-label to the component when used without any visually displayed text label.
  • <button> click event gets triggered on both Space and Enter key presses.
  • <a> click event gets triggered on Enter key press.