Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 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
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
126 changes: 126 additions & 0 deletions docs/6.x/docs/guides/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,129 @@ const theme = {
style={{ fontSize: 16, color: '#1C1B1F' }}
/>
```

### 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 matches the
only size 5.x had, so existing buttons keep their metrics.

```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

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

#### Removed props

- **`uppercase`** → `labelStyle={{ textTransform: 'uppercase' }}`.
- **`compact`** → `size="extra-small"`.
- **`contentStyle={{ flexDirection: 'row-reverse' }}`** → `iconPosition="trailing"`.
- **`elevation`** → elevation follows `mode="elevated"`.

```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`.
- **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>
```

#### 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**.

#### Card.Actions

`Card.Actions` defaults its buttons to `outlined` for the first child and
**`filled`** for the rest, where it used to default to `contained`. It also no
longer forwards `compact` or `uppercase` to them.
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 @@ -86,7 +86,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
2 changes: 1 addition & 1 deletion docs/6.x/docs/guides/ripple-effect.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The `rippleColor` prop is available for every pressable component which allows y
<Button
rippleColor="#FF000020"
icon="camera"
mode="contained"
mode="filled"
onPress={() => console.log('Pressed')}>
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