Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
d0c3945
refactor: add Button label prop and deprecate children
azizbecha May 12, 2026
ef3f3ad
refactor: migrate internal Button usages to label
azizbecha May 12, 2026
4f375b2
feat: add Button iconPosition prop
azizbecha May 12, 2026
83706fb
feat: derive Button ripple color from label color
azizbecha May 12, 2026
10045bf
perf: memoize Button's derived values and handlers
azizbecha May 12, 2026
a153882
chore(example): migrate example app Buttons to label prop
azizbecha May 12, 2026
79fb9fb
docs: update Button usage in guides to label prop
azizbecha May 12, 2026
487eea4
feat: add Button size prop (MD3 expressive)
azizbecha May 13, 2026
6201110
feat: add Button shape prop (MD3 expressive)
azizbecha May 13, 2026
ca6247a
feat: add Button selected toggle state (MD3 expressive)
azizbecha May 13, 2026
3ffd98f
chore(example): add ButtonExample sections for size, shape and toggle
azizbecha May 13, 2026
20f94fe
feat(button): localize iconPosition via useLocale
azizbecha May 20, 2026
f91ef13
refactor(button): unify naming to label and drop md3 style prefix
azizbecha May 20, 2026
b0a7456
fix(button): use outline and onSurfaceVariant for outlined mode
azizbecha May 20, 2026
82edc23
fix(button): align legacy icon size, padding and icon gap to MD3 spec
azizbecha May 20, 2026
5407b5d
refactor(button): extract per-size component tokens
azizbecha May 20, 2026
39dd912
refactor(button)!: rename modes to filled and tonal
azizbecha May 20, 2026
2164e74
refactor(button)!: default mode is now filled
azizbecha May 20, 2026
eda3c0c
fix(button): ensure 48dp minimum touch target for XS/S sizes
azizbecha May 20, 2026
8e9efd6
feat(button): animate shape morph on press and toggle
azizbecha May 20, 2026
ba79a34
chore(example): revamp Button example as an interactive playground
azizbecha May 21, 2026
108c2fd
docs(button): update for filled/tonal modes and outlined tokens
azizbecha May 21, 2026
c5a4146
Merge branch 'main' into feat/button-material-3-expressive
oleksandrzavarzin-callstack Aug 31, 2026
00bb5fa
fix: revert from Button label prop to children
oleksandrzavarzin-callstack Aug 31, 2026
06b6bc1
refactor: default to the small size and drop the unset size
oleksandrzavarzin-callstack Aug 31, 2026
0ba0f4f
refactor: remove uppercase, compact and contentStyle row-reverse
oleksandrzavarzin-callstack Aug 31, 2026
5debe50
refactor: source shape and spacing from component tokens
oleksandrzavarzin-callstack Aug 31, 2026
cbb4172
feat(button): add MD3 toggle colour sets
oleksandrzavarzin-callstack Sep 1, 2026
84cf1bf
refactor: move animations to Reanimated
oleksandrzavarzin-callstack Sep 1, 2026
5c0a748
feat: allow opting out of the shape morph
oleksandrzavarzin-callstack Sep 1, 2026
f11cb50
fix: decouple button static styles from the animation
oleksandrzavarzin-callstack Sep 1, 2026
d9e7b99
chore: revamp the Button playground controls
oleksandrzavarzin-callstack Sep 2, 2026
53f23d1
docs: add v6 button migration notes
oleksandrzavarzin-callstack Sep 2, 2026
a643b64
Merge remote-tracking branch 'upstream/main' into feat/button-materia…
oleksandrzavarzin-callstack Sep 2, 2026
c200935
fix: tighten the pressed corner per size
oleksandrzavarzin-callstack Sep 2, 2026
5e8c183
docs: attach the docblock and regenerate the page
oleksandrzavarzin-callstack Sep 2, 2026
07a32a9
docs: document Button's style restrictions in the migration guide
oleksandrzavarzin-callstack Sep 2, 2026
fc5c897
docs: correct the theme colors table
oleksandrzavarzin-callstack Sep 2, 2026
700d579
Merge branch 'main' into feat/button-material-3-expressive
oleksandrzavarzin-callstack Sep 2, 2026
8fbbf54
fix: update example custom ripple color for button
oleksandrzavarzin-callstack Sep 3, 2026
13d878e
fix: stop the container fading through gray on a mode change
oleksandrzavarzin-callstack Sep 3, 2026
f78e336
fix: drop the text toggle
oleksandrzavarzin-callstack Sep 3, 2026
160e817
fix: drop the inert 48dp hitSlop expansion
oleksandrzavarzin-callstack Sep 4, 2026
7cdc502
Merge remote-tracking branch 'upstream/main' into feat/button-materia…
oleksandrzavarzin-callstack Sep 7, 2026
8450323
fix: thicken the outlined stroke on l and xl buttons
oleksandrzavarzin-callstack Sep 7, 2026
2086efb
feat: expand the button touch target to the MD3 outer bound
oleksandrzavarzin-callstack Sep 7, 2026
1aaeebf
Merge remote-tracking branch 'upstream/main' into feat/button-materia…
oleksandrzavarzin-callstack Sep 11, 2026
ec06ea8
Merge remote-tracking branch 'upstream/main' into feat/button-materia…
oleksandrzavarzin-callstack Sep 14, 2026
48b63cd
Merge remote-tracking branch 'upstream/main' into feat/button-materia…
oleksandrzavarzin-callstack Sep 17, 2026
352c2d1
Merge remote-tracking branch 'upstream/main' into feat/button-materia…
oleksandrzavarzin-callstack Oct 16, 2026
0140fd0
fix: apply review suggestions from @satya164
oleksandrzavarzin-callstack Oct 23, 2026
ab66c5b
fix: set mode="text" on dialog actions after the filled default
oleksandrzavarzin-callstack Oct 23, 2026
8ef8538
docs: correct the size and padding notes for Button
oleksandrzavarzin-callstack Oct 23, 2026
57d5c9c
docs: correct the compact and elevation notes for Button
oleksandrzavarzin-callstack Oct 23, 2026
3c5c83f
fix: stop reading a ref during render in Button
oleksandrzavarzin-callstack Oct 26, 2026
f4e502a
refactor: animate the Button shape with a spring-shaped CSS transition
oleksandrzavarzin-callstack Oct 27, 2026
b274f62
test: assert on public Button output and drop redundant tests
oleksandrzavarzin-callstack Oct 27, 2026
c9cce66
feat: add aria-pressed on web with a toggle state
oleksandrzavarzin-callstack Oct 27, 2026
fce6bce
refactor: drop the duplicate selected tokens from Button
oleksandrzavarzin-callstack Oct 27, 2026
b76e128
refactor: drop the rippleColor prop from Button
oleksandrzavarzin-callstack Nov 6, 2026
318614e
fix: keep the Button icon on the requested side under web RTL
oleksandrzavarzin-callstack Nov 6, 2026
7db7f05
fix: paint the Button container once and snap PlatformColor changes
oleksandrzavarzin-callstack Nov 6, 2026
045e169
fix: handle custom Button colors in the ripple and the toggle label
oleksandrzavarzin-callstack Nov 6, 2026
29ba507
fix: announce both Button toggle states and fill disabled outlined ones
oleksandrzavarzin-callstack Nov 6, 2026
de816d8
fix: cap the Button touch target slop at 48dp
oleksandrzavarzin-callstack Nov 6, 2026
18b49c2
fix: keep the Button corner at 0 or above during the shape spring
oleksandrzavarzin-callstack Nov 6, 2026
f6a22de
docs: hide the Surface shape props and inline the Button unions
oleksandrzavarzin-callstack Nov 6, 2026
b88fc68
docs: note the Button style and contentStyle limits
oleksandrzavarzin-callstack Nov 6, 2026
64f3393
docs: recommend text buttons for Dialog.Actions in the migration guide
oleksandrzavarzin-callstack Nov 6, 2026
736ca49
Merge remote-tracking branch 'upstream/main' into feat/button-materia…
oleksandrzavarzin-callstack Nov 6, 2026
6a3b559
Merge remote-tracking branch 'upstream/main' into feat/button-materia…
oleksandrzavarzin-callstack Nov 6, 2026
d8c08a3
docs(button): document custom colors on toggle states
oleksandrzavarzin-callstack Nov 6, 2026
c756878
refactor(button): share one icon color with the loading indicator
oleksandrzavarzin-callstack Nov 6, 2026
8588913
refactor(button): share the isMode helper between Button and utils
oleksandrzavarzin-callstack Nov 6, 2026
c4787c6
Merge branch 'main' into feat/button-material-3-expressive
oleksandrzavarzin-callstack Nov 6, 2026
d06db9d
docs(migration): point Button roundness at the square shape
azizbecha Nov 7, 2026
d3f23b1
docs(theming): point Button corner examples at the square shape
azizbecha Nov 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 162 additions & 8 deletions docs/6.x/docs/guides/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,16 +203,20 @@ In 5.x, each of these components multiplied `roundness` by a fixed number, and t
| `extraSmall` | 4 | 1 | `Menu`, `Snackbar`, `Tooltip` |
| `small` | 8 | 2 | `Chip` |
| `medium` | 12 | 3 | `Card` |
| `largeIncreased` | 20 | 5 | `Button`, `SegmentedButtons` |
| `largeIncreased` | 20 | 5 | `SegmentedButtons` |
| `extraLarge` | 28 | 7 | `Dialog`, `Drawer.Item`, `Searchbar` (`mode="bar"`) |

`Button` (5.x multiplier 5) is a full pill by default and doesn't read a corner role. With `shape="square"` it reads the corner of its `size`, as listed in the Button section's [Style](#style) notes: `medium` at the default `small` size.

`FAB` and `FAB.Extended` use the role of their `size`: `large` (16) by default, `largeIncreased` for `size="medium"` and `extraLarge` for `size="large"`. `TextInput` no longer follows the theme: its corners are always 4.

A component's `theme` prop is merged deeply, so you can set only the role you need:

```diff
-<Button theme={{ roundness: 2 }}>Press me</Button>
+<Button theme={{ shapes: { corner: { largeIncreased: 10 } } }}>Press me</Button>
+<Button shape="square" theme={{ shapes: { corner: { medium: 10 } } }}>
+ Press me
+</Button>
```

`PaperProvider` doesn't merge `shapes` deeply: a `shapes` key in its `theme` replaces the default one. So spread the base `shapes` and `corner`, or the roles you leave out are `undefined` and those components lose their rounded corners:
Expand Down Expand Up @@ -424,7 +428,7 @@ When the dialog is `dismissable`, screen reader users can dismiss it with a visu

#### `Dialog.Actions`

`Dialog.Actions` no longer injects `compact` on its buttons. To keep the previous behavior, you need to add the `compact` prop to each button:
`Dialog.Actions` no longer injects `compact` on its buttons, and the default [`Button`](#button) mode is now `filled`. Add `mode="text"` to each button. At the default `small` size, the buttons are **40dp** tall, the Material Design 3 size for dialog actions. The `compact` text buttons in 5.x were **38dp** tall. For a more compact look, also add `size="extra-small"`, which is **32dp** tall:

```tsx
// Before (v5)
Expand All @@ -434,7 +438,7 @@ When the dialog is `dismissable`, screen reader users can dismiss it with a visu

// After (v6)
<Dialog.Actions>
<Button compact onPress={hide}>
<Button mode="text" onPress={hide}>
Done
</Button>
</Dialog.Actions>
Expand Down Expand Up @@ -599,6 +603,156 @@ const theme = {
/>
```

### Button

`Button` is modernized to Material Design 3 Expressive. The button modes were
renamed, the default mode changed, and a size and shape scale were added.

#### Mode

- **`mode="contained"`** → **`mode="filled"`**
- **`mode="contained-tonal"`** → **`mode="tonal"`**

```tsx
// Before (v5)
<Button mode="contained">Save</Button>
<Button mode="contained-tonal">Save</Button>

// After (v6)
<Button mode="filled">Save</Button>
<Button mode="tonal">Save</Button>
```

#### Default mode

The default is now **`filled`**, not `text`, matching the MD3 emphasis
hierarchy. Pass `mode="text"` to keep the old look.

```tsx
// Before (v5) - rendered as a text button
<Button>Cancel</Button>

// After (v6) - same appearance
<Button mode="text">Cancel</Button>
```

#### Size and shape

New `size` and `shape` props. `size` defaults to `small`, which keeps the
**40dp** height of the only size 5.x had. Its horizontal padding is different,
see the appearance changes below.

```tsx
<Button size="extra-small">XS</Button>
<Button size="large" shape="square">Large, square</Button>
```

The container now also morphs its corner while pressed. Pass
`animateShape={false}` to opt out.

#### Toggle

`selected` turns a button into a toggle. Leave it **undefined** to make it
a plain button. It is ignored on `mode="text"`, which MD3 gives no toggle.

```tsx
<Button mode="filled" selected={isOn} onPress={toggle}>
Bold
</Button>
```

#### Removed props

- **`uppercase`** → `labelStyle={{ textTransform: 'uppercase' }}`.
- **`compact`** → `size="extra-small"`. This also changes the height:
`extra-small` is **32dp** tall, while `compact` only reduced the horizontal
margins and the min width, and kept the height: **40dp**, or **38dp** for
`text` buttons.
- **`contentStyle={{ flexDirection: 'row-reverse' }}`** → `iconPosition="trailing"`.
- **`rippleColor`** is removed, with no replacement. The ripple uses the label
color at the pressed-state opacity.

```tsx
// Before (v5)
<Button uppercase compact contentStyle={{ flexDirection: 'row-reverse' }} icon="chevron-right">
Next
</Button>

// After (v6)
<Button
size="extra-small"
iconPosition="trailing"
icon="chevron-right"
labelStyle={{ textTransform: 'uppercase' }}
>
Next
</Button>
```

#### Style

`Button` renders on `Surface`, so its `style` follows the same rules:

- **Border radius** is no longer set through `style`. Use `shape`, or override
the corner token that the size maps to. `round` is the full pill; `square`
reads `corner.medium` at `extra-small` and `small`; `corner.large` at
`medium`; and `corner.extraLarge` at `large` and `extra-large`.
- **Background color** is no longer set through `style`. Use `buttonColor`.
- **Border color and width** are no longer set through `style`. An inner view
draws the outline, so `borderColor` in `style` does not recolor it, and
`borderWidth` adds a second border around it. To recolor the outline,
override `theme.colors.outlineVariant`.
- **Animated styles** must come from Reanimated. A React Native
`Animated.Value` in `style` is not applied.

```tsx
// Before (v5)
<Button style={{ borderRadius: 8, backgroundColor: 'red' }}>
Press me
</Button>

// After (v6)
<Button
shape="square"
theme={{ shapes: { corner: { medium: 8 } } }}
buttonColor="red"
>
Press me
</Button>
```

#### Content style

The content takes `minHeight`, `paddingStart` and `paddingEnd` from `size`.
To change them in `contentStyle`, set these same properties. On Android and
iOS, `padding` and `paddingHorizontal` do not override them, and a `height`
below `minHeight` has no effect.

```tsx
// Before (v5)
<Button contentStyle={{ height: 36, paddingHorizontal: 8 }}>Press me</Button>

// After (v6)
<Button contentStyle={{ minHeight: 36, paddingStart: 8, paddingEnd: 8 }}>
Press me
</Button>
```

#### Appearance changes

These need no code change, but the rendering differs:

- The `outlined` label and icon use **`onSurfaceVariant`** instead of
`primary`.
- The default icon size is **20dp**, up from **18dp**.
- `labelStyle={{ fontSize }}` no longer changes the icon size. The icon follows
`size`; set both if you need them to match.
- The resting corner is the full pill radius instead of a fixed **20dp**.
- At the default `small` size, the horizontal padding is **16dp** for every
mode. In 5.x, the label margins were **24dp** for `filled`, `tonal`,
`elevated` and `outlined`, and **12dp** for `text` (**16dp** with an icon).
Existing buttons change width.

### Card

#### `Card` layout
Expand All @@ -622,7 +776,7 @@ This means, in a typical card layout, the Card will automatically apply the nece
<Text>Card content</Text>
</Card.Content>
<Card.Actions>
<Button mode="contained">Action</Button>
<Button mode="filled">Action</Button>
<Button mode="outlined">Another Action</Button>
</Card.Actions>
</Card>
Expand Down Expand Up @@ -669,7 +823,7 @@ We have also added a `direction` prop to the `Card` component, which lets you ar
<Text>Card content</Text>
</Card.Content>
<Card.Actions>
<Button mode="contained">Action</Button>
<Button mode="filled">Action</Button>
<Button mode="outlined">Another Action</Button>
</Card.Actions>
</Card>
Expand All @@ -686,7 +840,7 @@ The `style` prop of `Card.Cover` is now typed as a `View` style. Image style pro

#### `Card.Actions`

`Card.Actions` no longer injects `mode` on the buttons. To keep the previous behavior, you need to set `mode="outlined"` on the first button and `mode="contained"` on the rest:
`Card.Actions` no longer injects `mode` on the buttons. To keep the previous behavior, you need to set `mode="outlined"` on the first button and `mode="filled"` (formerly `contained`) on the rest:

```tsx
// Before (v5)
Expand All @@ -698,7 +852,7 @@ The `style` prop of `Card.Cover` is now typed as a `View` style. Image style pro
// After (v6)
<Card.Actions>
<Button mode="outlined">Cancel</Button>
<Button mode="contained">Ok</Button>
<Button mode="filled">Ok</Button>
</Card.Actions>
```

Expand Down
2 changes: 1 addition & 1 deletion docs/6.x/docs/guides/react-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function HomeScreen({ navigation }) {
return (
<View style={style.container}>
<Text>Home Screen</Text>
<Button mode="contained" onPress={() => navigation.navigate('Details')}>
<Button mode="filled" onPress={() => navigation.navigate('Details')}>
Go to details
</Button>
</View>
Expand Down
6 changes: 3 additions & 3 deletions docs/6.x/docs/guides/theming.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ You can change the theme prop dynamically and all the components will automatica
A theme usually contains the following properties:

- `dark` (`boolean`): whether this is a dark theme or light theme.
- `shapes` (`object`): corner radii of components, under `corner`, from `extraSmall` (4) to `extraExtraLarge` (48). Each component uses one of them, e.g. `Button` uses `largeIncreased`.
- `shapes` (`object`): corner radii of components, under `corner`, from `extraSmall` (4) to `extraExtraLarge` (48). Each component uses one of them, e.g. `Card` uses `medium`. `Button` is a full pill by default; with `shape="square"` it uses the role of its `size`, `medium` at the default `small` size.
- `colors` (`object`): various colors used throughout different elements.

> The primary key color is used to derive roles for key components across the UI, such as the FAB, prominent buttons, active states, as well as the tint of elevated surfaces.
Expand Down Expand Up @@ -473,8 +473,8 @@ import { Button } from 'react-native-paper';
export default function ButtonExample() {
return (
<Button
mode="contained"
theme={{ shapes: { corner: { largeIncreased: 15 } } }}
shape="square"
theme={{ shapes: { corner: { medium: 15 } } }}
>
Press me
</Button>
Expand Down
4 changes: 2 additions & 2 deletions docs/src/components/BannerExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ const BannerExample = () => {
<Button loading onPress={() => {}}>
Loading
</Button>
<Button mode="contained-tonal" icon="camera" onPress={() => {}}>
<Button mode="tonal" icon="camera" onPress={() => {}}>
Icon
</Button>
<Button icon="camera" mode="contained" onPress={() => {}}>
<Button icon="camera" mode="filled" onPress={() => {}}>
Press me
</Button>
<FAB icon="plus" size="default" onPress={() => {}} />
Expand Down
8 changes: 4 additions & 4 deletions docs/src/data/screenshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ export const screenshots = {
BottomNavigation: 'screenshots/bottom-navigation.gif',
'BottomNavigation.Bar': 'screenshots/bottom-navigation-tabs.jpg',
Button: {
text: 'screenshots/button-1.png',
outlined: 'screenshots/button-2.png',
contained: 'screenshots/button-3.png',
filled: 'screenshots/button-3.png',
tonal: 'screenshots/button-5.png',
elevated: 'screenshots/button-4.png',
'contained-tonal': 'screenshots/button-5.png',
outlined: 'screenshots/button-2.png',
text: 'screenshots/button-1.png',
},
Card: {
elevated: 'screenshots/card-1.png',
Expand Down
76 changes: 56 additions & 20 deletions docs/src/data/themeColors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,46 +46,82 @@ export const themeColors = {
},
},
Button: {
active: {
default: {
filled: {
backgroundColor: 'theme.colors.primary',
textColor: 'theme.colors.onPrimary',
},
tonal: {
backgroundColor: 'theme.colors.secondaryContainer',
textColor: 'theme.colors.onSecondaryContainer',
},
elevated: {
backgroundColor: 'theme.colors.elevation.level1',
backgroundColor: 'theme.colors.surfaceContainerLow',
textColor: 'theme.colors.primary',
},
contained: {
outlined: {
textColor: 'theme.colors.onSurfaceVariant',
borderColor: 'theme.colors.outlineVariant',
},
text: {
textColor: 'theme.colors.primary',
},
},
selected: {
filled: {
backgroundColor: 'theme.colors.primary',
textColor: 'theme.colors.onPrimary',
},
'contained-tonal': {
tonal: {
backgroundColor: 'theme.colors.secondary',
textColor: 'theme.colors.onSecondary',
},
elevated: {
backgroundColor: 'theme.colors.primary',
textColor: 'theme.colors.onPrimary',
},
outlined: {
backgroundColor: 'theme.colors.inverseSurface',
textColor: 'theme.colors.inverseOnSurface',
},
},
unselected: {
filled: {
backgroundColor: 'theme.colors.surfaceContainer',
textColor: 'theme.colors.onSurfaceVariant',
},
tonal: {
backgroundColor: 'theme.colors.secondaryContainer',
textColor: 'theme.colors.onSecondaryContainer',
},
outlined: {
elevated: {
backgroundColor: 'theme.colors.surfaceContainerLow',
textColor: 'theme.colors.primary',
borderColor: 'theme.colors.outline',
},
text: {
textColor: 'theme.colors.primary',
outlined: {
textColor: 'theme.colors.onSurfaceVariant',
borderColor: 'theme.colors.outlineVariant',
},
},
disabled: {
elevated: {
backgroundColor: 'theme.colors.surfaceDisabled',
textColor: 'theme.colors.onSurfaceDisabled',
filled: {
backgroundColor: 'theme.colors.onSurface',
textColor: 'theme.colors.onSurface',
},
contained: {
backgroundColor: 'theme.colors.surfaceDisabled',
textColor: 'theme.colors.onSurfaceDisabled',
tonal: {
backgroundColor: 'theme.colors.onSurface',
textColor: 'theme.colors.onSurface',
},
'contained-tonal': {
backgroundColor: 'theme.colors.surfaceDisabled',
textColor: 'theme.colors.onSurfaceDisabled',
elevated: {
backgroundColor: 'theme.colors.onSurface',
textColor: 'theme.colors.onSurface',
},
outlined: {
textColor: 'theme.colors.onSurfaceDisabled',
borderColor: 'theme.colors.surfaceDisabled',
textColor: 'theme.colors.onSurface',
borderColor: 'theme.colors.outlineVariant',
},
text: {
textColor: 'theme.colors.onSurfaceDisabled',
textColor: 'theme.colors.onSurface',
},
},
},
Expand Down
Loading
Loading