about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2025-07-31 10:32:16 -0500
committerGitHub <noreply@github.com>2025-07-31 10:32:16 -0500
commite87555c4816c14752e944092d373ee53bbc05632 (patch)
treea54da285cae588a464d47b22d5f658a0771f8b70
parent3bcfcba6d8176bac03202b496110915da748b0f1 (diff)
downloadvoidsky-e87555c4816c14752e944092d373ee53bbc05632.tar.zst
[APP-1310] Button cleanup (#8754)
* Rm gradient buttons from Storybook

* TEMP move storybook button section

* Remove gradient_sky

* Remove actual defs for gradient_sky and gradient_primary

* Remove other gradient defs

* Remove gradient support entirely

* Deprecate 'variant' in favor of 'color'

* Fork base styles codepath to make variant deprecation more obvious

* Remove text styles for when no color is set, never been used

* Fork text styles codepath to make variant deprecation more obvious

* Revert temp storybook commit, remove deprecated values

* Replace remaining gradient button usage
-rw-r--r--src/components/Button.tsx548
-rw-r--r--src/components/Lists.tsx7
-rw-r--r--src/screens/Onboarding/StepFinished.tsx4
-rw-r--r--src/screens/Onboarding/StepInterests/index.tsx6
-rw-r--r--src/screens/Onboarding/StepProfile/index.tsx4
-rw-r--r--src/view/screens/Storybook/Buttons.tsx210
-rw-r--r--src/view/screens/Storybook/Forms.tsx6
-rw-r--r--src/view/screens/Storybook/index.tsx7
8 files changed, 312 insertions, 480 deletions
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index 22c9ab96d..57a8530b5 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -14,9 +14,8 @@ import {
   View,
   type ViewStyle,
 } from 'react-native'
-import {LinearGradient} from 'expo-linear-gradient'
 
-import {atoms as a, flatten, select, tokens, useTheme} from '#/alf'
+import {atoms as a, flatten, select, useTheme} from '#/alf'
 import {type Props as SVGIconProps} from '#/components/icons/common'
 import {Text} from '#/components/Typography'
 
@@ -32,25 +31,19 @@ import {Text} from '#/components/Typography'
  */
 export type UninheritableButtonProps = 'variant' | 'color' | 'size' | 'shape'
 
-export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient'
+export type ButtonVariant = 'solid' | 'outline' | 'ghost'
 export type ButtonColor =
   | 'primary'
   | 'secondary'
   | 'secondary_inverted'
   | 'negative'
   | 'negative_secondary'
-  | 'gradient_primary'
-  | 'gradient_sky'
-  | 'gradient_midnight'
-  | 'gradient_sunrise'
-  | 'gradient_sunset'
-  | 'gradient_nordic'
-  | 'gradient_bonfire'
 export type ButtonSize = 'tiny' | 'small' | 'large'
 export type ButtonShape = 'round' | 'square' | 'default'
 export type VariantProps = {
   /**
    * The style variation of the button
+   * @deprecated Use `color` instead.
    */
   variant?: ButtonVariant
   /**
@@ -143,6 +136,15 @@ export const Button = React.forwardRef<View, ButtonProps>(
     },
     ref,
   ) => {
+    /**
+     * The `variant` prop is deprecated in favor of simply specifying `color`.
+     * If a `color` is set, then we want to use the existing codepaths for
+     * "solid" buttons. This is to maintain backwards compatibility.
+     */
+    if (!variant && color) {
+      variant = 'solid'
+    }
+
     const t = useTheme()
     const [state, setState] = React.useState({
       pressed: false,
@@ -215,8 +217,13 @@ export const Button = React.forwardRef<View, ButtonProps>(
       const baseStyles: ViewStyle[] = []
       const hoverStyles: ViewStyle[] = []
 
-      if (color === 'primary') {
-        if (variant === 'solid') {
+      /*
+       * This is the happy path for new button styles, following the
+       * deprecation of `variant` prop. This redundant `variant` check is here
+       * just to make this handling easier to understand.
+       */
+      if (variant === 'solid') {
+        if (color === 'primary') {
           if (!disabled) {
             baseStyles.push({
               backgroundColor: t.palette.primary_500,
@@ -233,64 +240,14 @@ export const Button = React.forwardRef<View, ButtonProps>(
               }),
             })
           }
-        } else if (variant === 'outline') {
-          baseStyles.push(a.border, t.atoms.bg, {
-            borderWidth: 1,
-          })
-
-          if (!disabled) {
-            baseStyles.push(a.border, {
-              borderColor: t.palette.primary_500,
-            })
-            hoverStyles.push(a.border, {
-              backgroundColor: t.palette.primary_50,
-            })
-          } else {
-            baseStyles.push(a.border, {
-              borderColor: t.palette.primary_200,
-            })
-          }
-        } else if (variant === 'ghost') {
-          if (!disabled) {
-            baseStyles.push(t.atoms.bg)
-            hoverStyles.push({
-              backgroundColor: t.palette.primary_100,
-            })
-          }
-        }
-      } else if (color === 'secondary') {
-        if (variant === 'solid') {
+        } else if (color === 'secondary') {
           if (!disabled) {
             baseStyles.push(t.atoms.bg_contrast_25)
             hoverStyles.push(t.atoms.bg_contrast_50)
           } else {
             baseStyles.push(t.atoms.bg_contrast_100)
           }
-        } else if (variant === 'outline') {
-          baseStyles.push(a.border, t.atoms.bg, {
-            borderWidth: 1,
-          })
-
-          if (!disabled) {
-            baseStyles.push(a.border, {
-              borderColor: t.palette.contrast_300,
-            })
-            hoverStyles.push(t.atoms.bg_contrast_50)
-          } else {
-            baseStyles.push(a.border, {
-              borderColor: t.palette.contrast_200,
-            })
-          }
-        } else if (variant === 'ghost') {
-          if (!disabled) {
-            baseStyles.push(t.atoms.bg)
-            hoverStyles.push({
-              backgroundColor: t.palette.contrast_25,
-            })
-          }
-        }
-      } else if (color === 'secondary_inverted') {
-        if (variant === 'solid') {
+        } else if (color === 'secondary_inverted') {
           if (!disabled) {
             baseStyles.push({
               backgroundColor: t.palette.contrast_900,
@@ -303,31 +260,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
               backgroundColor: t.palette.contrast_600,
             })
           }
-        } else if (variant === 'outline') {
-          baseStyles.push(a.border, t.atoms.bg, {
-            borderWidth: 1,
-          })
-
-          if (!disabled) {
-            baseStyles.push(a.border, {
-              borderColor: t.palette.contrast_300,
-            })
-            hoverStyles.push(t.atoms.bg_contrast_50)
-          } else {
-            baseStyles.push(a.border, {
-              borderColor: t.palette.contrast_200,
-            })
-          }
-        } else if (variant === 'ghost') {
-          if (!disabled) {
-            baseStyles.push(t.atoms.bg)
-            hoverStyles.push({
-              backgroundColor: t.palette.contrast_25,
-            })
-          }
-        }
-      } else if (color === 'negative') {
-        if (variant === 'solid') {
+        } else if (color === 'negative') {
           if (!disabled) {
             baseStyles.push({
               backgroundColor: t.palette.negative_500,
@@ -344,33 +277,7 @@ export const Button = React.forwardRef<View, ButtonProps>(
               }),
             })
           }
-        } else if (variant === 'outline') {
-          baseStyles.push(a.border, t.atoms.bg, {
-            borderWidth: 1,
-          })
-
-          if (!disabled) {
-            baseStyles.push(a.border, {
-              borderColor: t.palette.negative_500,
-            })
-            hoverStyles.push(a.border, {
-              backgroundColor: t.palette.negative_50,
-            })
-          } else {
-            baseStyles.push(a.border, {
-              borderColor: t.palette.negative_200,
-            })
-          }
-        } else if (variant === 'ghost') {
-          if (!disabled) {
-            baseStyles.push(t.atoms.bg)
-            hoverStyles.push({
-              backgroundColor: t.palette.negative_100,
-            })
-          }
-        }
-      } else if (color === 'negative_secondary') {
-        if (variant === 'solid') {
+        } else if (color === 'negative_secondary') {
           if (!disabled) {
             baseStyles.push({
               backgroundColor: select(t.name, {
@@ -395,31 +302,141 @@ export const Button = React.forwardRef<View, ButtonProps>(
               }),
             })
           }
-        } else if (variant === 'outline') {
-          baseStyles.push(a.border, t.atoms.bg, {
-            borderWidth: 1,
-          })
+        }
+      } else {
+        /*
+         * BEGIN DEPRECATED STYLES
+         */
+        if (color === 'primary') {
+          if (variant === 'outline') {
+            baseStyles.push(a.border, t.atoms.bg, {
+              borderWidth: 1,
+            })
 
-          if (!disabled) {
-            baseStyles.push(a.border, {
-              borderColor: t.palette.negative_500,
+            if (!disabled) {
+              baseStyles.push(a.border, {
+                borderColor: t.palette.primary_500,
+              })
+              hoverStyles.push(a.border, {
+                backgroundColor: t.palette.primary_50,
+              })
+            } else {
+              baseStyles.push(a.border, {
+                borderColor: t.palette.primary_200,
+              })
+            }
+          } else if (variant === 'ghost') {
+            if (!disabled) {
+              baseStyles.push(t.atoms.bg)
+              hoverStyles.push({
+                backgroundColor: t.palette.primary_100,
+              })
+            }
+          }
+        } else if (color === 'secondary') {
+          if (variant === 'outline') {
+            baseStyles.push(a.border, t.atoms.bg, {
+              borderWidth: 1,
             })
-            hoverStyles.push(a.border, {
-              backgroundColor: t.palette.negative_50,
+
+            if (!disabled) {
+              baseStyles.push(a.border, {
+                borderColor: t.palette.contrast_300,
+              })
+              hoverStyles.push(t.atoms.bg_contrast_50)
+            } else {
+              baseStyles.push(a.border, {
+                borderColor: t.palette.contrast_200,
+              })
+            }
+          } else if (variant === 'ghost') {
+            if (!disabled) {
+              baseStyles.push(t.atoms.bg)
+              hoverStyles.push({
+                backgroundColor: t.palette.contrast_25,
+              })
+            }
+          }
+        } else if (color === 'secondary_inverted') {
+          if (variant === 'outline') {
+            baseStyles.push(a.border, t.atoms.bg, {
+              borderWidth: 1,
             })
-          } else {
-            baseStyles.push(a.border, {
-              borderColor: t.palette.negative_200,
+
+            if (!disabled) {
+              baseStyles.push(a.border, {
+                borderColor: t.palette.contrast_300,
+              })
+              hoverStyles.push(t.atoms.bg_contrast_50)
+            } else {
+              baseStyles.push(a.border, {
+                borderColor: t.palette.contrast_200,
+              })
+            }
+          } else if (variant === 'ghost') {
+            if (!disabled) {
+              baseStyles.push(t.atoms.bg)
+              hoverStyles.push({
+                backgroundColor: t.palette.contrast_25,
+              })
+            }
+          }
+        } else if (color === 'negative') {
+          if (variant === 'outline') {
+            baseStyles.push(a.border, t.atoms.bg, {
+              borderWidth: 1,
             })
+
+            if (!disabled) {
+              baseStyles.push(a.border, {
+                borderColor: t.palette.negative_500,
+              })
+              hoverStyles.push(a.border, {
+                backgroundColor: t.palette.negative_50,
+              })
+            } else {
+              baseStyles.push(a.border, {
+                borderColor: t.palette.negative_200,
+              })
+            }
+          } else if (variant === 'ghost') {
+            if (!disabled) {
+              baseStyles.push(t.atoms.bg)
+              hoverStyles.push({
+                backgroundColor: t.palette.negative_100,
+              })
+            }
           }
-        } else if (variant === 'ghost') {
-          if (!disabled) {
-            baseStyles.push(t.atoms.bg)
-            hoverStyles.push({
-              backgroundColor: t.palette.negative_100,
+        } else if (color === 'negative_secondary') {
+          if (variant === 'outline') {
+            baseStyles.push(a.border, t.atoms.bg, {
+              borderWidth: 1,
             })
+
+            if (!disabled) {
+              baseStyles.push(a.border, {
+                borderColor: t.palette.negative_500,
+              })
+              hoverStyles.push(a.border, {
+                backgroundColor: t.palette.negative_50,
+              })
+            } else {
+              baseStyles.push(a.border, {
+                borderColor: t.palette.negative_200,
+              })
+            }
+          } else if (variant === 'ghost') {
+            if (!disabled) {
+              baseStyles.push(t.atoms.bg)
+              hoverStyles.push({
+                backgroundColor: t.palette.negative_100,
+              })
+            }
           }
         }
+        /*
+         * END DEPRECATED STYLES
+         */
       }
 
       if (shape === 'default') {
@@ -483,49 +500,6 @@ export const Button = React.forwardRef<View, ButtonProps>(
       }
     }, [t, variant, color, size, shape, disabled])
 
-    const gradientValues = React.useMemo(() => {
-      const gradient = {
-        primary: tokens.gradients.sky,
-        secondary: tokens.gradients.sky,
-        secondary_inverted: tokens.gradients.sky,
-        negative: tokens.gradients.sky,
-        negative_secondary: tokens.gradients.sky,
-        gradient_primary: tokens.gradients.primary,
-        gradient_sky: tokens.gradients.sky,
-        gradient_midnight: tokens.gradients.midnight,
-        gradient_sunrise: tokens.gradients.sunrise,
-        gradient_sunset: tokens.gradients.sunset,
-        gradient_nordic: tokens.gradients.nordic,
-        gradient_bonfire: tokens.gradients.bonfire,
-      }[color || 'primary']
-
-      if (variant === 'gradient') {
-        if (gradient.values.length < 2) {
-          throw new Error(
-            'Gradient buttons must have at least two colors in the gradient',
-          )
-        }
-
-        return {
-          colors: gradient.values.map(([_, color]) => color) as [
-            string,
-            string,
-            ...string[],
-          ],
-          hoverColors: gradient.values.map(_ => gradient.hover_value) as [
-            string,
-            string,
-            ...string[],
-          ],
-          locations: gradient.values.map(([location, _]) => location) as [
-            number,
-            number,
-            ...number[],
-          ],
-        }
-      }
-    }, [variant, color])
-
     const context = React.useMemo<ButtonContext>(
       () => ({
         ...state,
@@ -568,27 +542,6 @@ export const Button = React.forwardRef<View, ButtonProps>(
         onHoverOut={onHoverOut}
         onFocus={onFocus}
         onBlur={onBlur}>
-        {variant === 'gradient' && gradientValues && (
-          <View
-            style={[
-              a.absolute,
-              a.inset_0,
-              a.overflow_hidden,
-              {borderRadius: flattenedBaseStyles.borderRadius},
-            ]}>
-            <LinearGradient
-              colors={
-                state.hovered || state.pressed
-                  ? gradientValues.hoverColors
-                  : gradientValues.colors
-              }
-              locations={gradientValues.locations}
-              start={{x: 0, y: 0}}
-              end={{x: 1, y: 1}}
-              style={[a.absolute, a.inset_0]}
-            />
-          </View>
-        )}
         <Context.Provider value={context}>
           {typeof children === 'function' ? children(context) : children}
         </Context.Provider>
@@ -604,30 +557,19 @@ export function useSharedButtonTextStyles() {
   return React.useMemo(() => {
     const baseStyles: TextStyle[] = []
 
-    if (color === 'primary') {
-      if (variant === 'solid') {
+    /*
+     * This is the happy path for new button styles, following the
+     * deprecation of `variant` prop. This redundant `variant` check is here
+     * just to make this handling easier to understand.
+     */
+    if (variant === 'solid') {
+      if (color === 'primary') {
         if (!disabled) {
           baseStyles.push({color: t.palette.white})
         } else {
           baseStyles.push({color: t.palette.white, opacity: 0.5})
         }
-      } else if (variant === 'outline') {
-        if (!disabled) {
-          baseStyles.push({
-            color: t.palette.primary_600,
-          })
-        } else {
-          baseStyles.push({color: t.palette.primary_600, opacity: 0.5})
-        }
-      } else if (variant === 'ghost') {
-        if (!disabled) {
-          baseStyles.push({color: t.palette.primary_600})
-        } else {
-          baseStyles.push({color: t.palette.primary_600, opacity: 0.5})
-        }
-      }
-    } else if (color === 'secondary') {
-      if (variant === 'solid' || variant === 'gradient') {
+      } else if (color === 'secondary') {
         if (!disabled) {
           baseStyles.push({
             color: t.palette.contrast_700,
@@ -637,29 +579,7 @@ export function useSharedButtonTextStyles() {
             color: t.palette.contrast_400,
           })
         }
-      } else if (variant === 'outline') {
-        if (!disabled) {
-          baseStyles.push({
-            color: t.palette.contrast_600,
-          })
-        } else {
-          baseStyles.push({
-            color: t.palette.contrast_300,
-          })
-        }
-      } else if (variant === 'ghost') {
-        if (!disabled) {
-          baseStyles.push({
-            color: t.palette.contrast_600,
-          })
-        } else {
-          baseStyles.push({
-            color: t.palette.contrast_300,
-          })
-        }
-      }
-    } else if (color === 'secondary_inverted') {
-      if (variant === 'solid' || variant === 'gradient') {
+      } else if (color === 'secondary_inverted') {
         if (!disabled) {
           baseStyles.push({
             color: t.palette.contrast_50,
@@ -669,49 +589,13 @@ export function useSharedButtonTextStyles() {
             color: t.palette.contrast_400,
           })
         }
-      } else if (variant === 'outline') {
-        if (!disabled) {
-          baseStyles.push({
-            color: t.palette.contrast_600,
-          })
-        } else {
-          baseStyles.push({
-            color: t.palette.contrast_300,
-          })
-        }
-      } else if (variant === 'ghost') {
-        if (!disabled) {
-          baseStyles.push({
-            color: t.palette.contrast_600,
-          })
-        } else {
-          baseStyles.push({
-            color: t.palette.contrast_300,
-          })
-        }
-      }
-    } else if (color === 'negative') {
-      if (variant === 'solid' || variant === 'gradient') {
+      } else if (color === 'negative') {
         if (!disabled) {
           baseStyles.push({color: t.palette.white})
         } else {
           baseStyles.push({color: t.palette.white, opacity: 0.5})
         }
-      } else if (variant === 'outline') {
-        if (!disabled) {
-          baseStyles.push({color: t.palette.negative_400})
-        } else {
-          baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
-        }
-      } else if (variant === 'ghost') {
-        if (!disabled) {
-          baseStyles.push({color: t.palette.negative_400})
-        } else {
-          baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
-        }
-      }
-    } else if (color === 'negative_secondary') {
-      if (variant === 'solid' || variant === 'gradient') {
+      } else if (color === 'negative_secondary') {
         if (!disabled) {
           baseStyles.push({
             color: select(t.name, {
@@ -730,25 +614,103 @@ export function useSharedButtonTextStyles() {
             opacity: 0.5,
           })
         }
-      } else if (variant === 'outline') {
-        if (!disabled) {
-          baseStyles.push({color: t.palette.negative_400})
-        } else {
-          baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
-        }
-      } else if (variant === 'ghost') {
-        if (!disabled) {
-          baseStyles.push({color: t.palette.negative_400})
-        } else {
-          baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
-        }
       }
     } else {
-      if (!disabled) {
-        baseStyles.push({color: t.palette.white})
-      } else {
-        baseStyles.push({color: t.palette.white, opacity: 0.5})
+      /*
+       * BEGIN DEPRECATED STYLES
+       */
+      if (color === 'primary') {
+        if (variant === 'outline') {
+          if (!disabled) {
+            baseStyles.push({
+              color: t.palette.primary_600,
+            })
+          } else {
+            baseStyles.push({color: t.palette.primary_600, opacity: 0.5})
+          }
+        } else if (variant === 'ghost') {
+          if (!disabled) {
+            baseStyles.push({color: t.palette.primary_600})
+          } else {
+            baseStyles.push({color: t.palette.primary_600, opacity: 0.5})
+          }
+        }
+      } else if (color === 'secondary') {
+        if (variant === 'outline') {
+          if (!disabled) {
+            baseStyles.push({
+              color: t.palette.contrast_600,
+            })
+          } else {
+            baseStyles.push({
+              color: t.palette.contrast_300,
+            })
+          }
+        } else if (variant === 'ghost') {
+          if (!disabled) {
+            baseStyles.push({
+              color: t.palette.contrast_600,
+            })
+          } else {
+            baseStyles.push({
+              color: t.palette.contrast_300,
+            })
+          }
+        }
+      } else if (color === 'secondary_inverted') {
+        if (variant === 'outline') {
+          if (!disabled) {
+            baseStyles.push({
+              color: t.palette.contrast_600,
+            })
+          } else {
+            baseStyles.push({
+              color: t.palette.contrast_300,
+            })
+          }
+        } else if (variant === 'ghost') {
+          if (!disabled) {
+            baseStyles.push({
+              color: t.palette.contrast_600,
+            })
+          } else {
+            baseStyles.push({
+              color: t.palette.contrast_300,
+            })
+          }
+        }
+      } else if (color === 'negative') {
+        if (variant === 'outline') {
+          if (!disabled) {
+            baseStyles.push({color: t.palette.negative_400})
+          } else {
+            baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
+          }
+        } else if (variant === 'ghost') {
+          if (!disabled) {
+            baseStyles.push({color: t.palette.negative_400})
+          } else {
+            baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
+          }
+        }
+      } else if (color === 'negative_secondary') {
+        if (variant === 'outline') {
+          if (!disabled) {
+            baseStyles.push({color: t.palette.negative_400})
+          } else {
+            baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
+          }
+        } else if (variant === 'ghost') {
+          if (!disabled) {
+            baseStyles.push({color: t.palette.negative_400})
+          } else {
+            baseStyles.push({color: t.palette.negative_400, opacity: 0.5})
+          }
+        }
       }
+      /*
+       * END DEPRECATED STYLES
+       */
     }
 
     if (size === 'large') {
diff --git a/src/components/Lists.tsx b/src/components/Lists.tsx
index 5c602249b..311df3bcb 100644
--- a/src/components/Lists.tsx
+++ b/src/components/Lists.tsx
@@ -1,7 +1,8 @@
-import React, {memo} from 'react'
-import {StyleProp, View, ViewStyle} from 'react-native'
+import {memo} from 'react'
+import {type StyleProp, View, type ViewStyle} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
+import type React from 'react'
 
 import {cleanError} from '#/lib/strings/errors'
 import {CenteredView} from '#/view/com/util/Views'
@@ -95,7 +96,7 @@ function ListFooterMaybeError({
           )}
         </Text>
         <Button
-          variant="gradient"
+          variant="solid"
           label={_(msg`Press to retry`)}
           style={[
             a.align_center,
diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx
index b30f61053..fa45baa65 100644
--- a/src/screens/Onboarding/StepFinished.tsx
+++ b/src/screens/Onboarding/StepFinished.tsx
@@ -305,8 +305,8 @@ export function StepFinished() {
         <Button
           disabled={saving}
           key={state.activeStep} // remove focus state on nav
-          variant="gradient"
-          color="gradient_sky"
+          variant="solid"
+          color="primary"
           size="large"
           label={_(msg`Complete onboarding and start using your account`)}
           onPress={finishOnboarding}>
diff --git a/src/screens/Onboarding/StepInterests/index.tsx b/src/screens/Onboarding/StepInterests/index.tsx
index 2f41433aa..2a121cac6 100644
--- a/src/screens/Onboarding/StepInterests/index.tsx
+++ b/src/screens/Onboarding/StepInterests/index.tsx
@@ -15,7 +15,7 @@ import {
   TitleText,
 } from '#/screens/Onboarding/Layout'
 import {
-  ApiResponseMap,
+  type ApiResponseMap,
   Context,
   useInterestsDisplayNames,
 } from '#/screens/Onboarding/state'
@@ -235,8 +235,8 @@ export function StepInterests() {
         ) : (
           <Button
             disabled={saving || !data}
-            variant="gradient"
-            color="gradient_sky"
+            variant="solid"
+            color="primary"
             size="large"
             label={_(msg`Continue to next step`)}
             onPress={saveInterests}>
diff --git a/src/screens/Onboarding/StepProfile/index.tsx b/src/screens/Onboarding/StepProfile/index.tsx
index 3d2c551e9..555507689 100644
--- a/src/screens/Onboarding/StepProfile/index.tsx
+++ b/src/screens/Onboarding/StepProfile/index.tsx
@@ -267,8 +267,8 @@ export function StepProfile() {
         <OnboardingControls.Portal>
           <View style={[a.gap_md, gtMobile && {flexDirection: 'row-reverse'}]}>
             <Button
-              variant="gradient"
-              color="gradient_sky"
+              variant="solid"
+              color="primary"
               size="large"
               label={_(msg`Continue to next step`)}
               onPress={onContinue}>
diff --git a/src/view/screens/Storybook/Buttons.tsx b/src/view/screens/Storybook/Buttons.tsx
index 98c16d144..eaf8bba7e 100644
--- a/src/view/screens/Storybook/Buttons.tsx
+++ b/src/view/screens/Storybook/Buttons.tsx
@@ -1,4 +1,3 @@
-import React from 'react'
 import {View} from 'react-native'
 
 import {atoms as a} from '#/alf'
@@ -7,7 +6,6 @@ import {
   type ButtonColor,
   ButtonIcon,
   ButtonText,
-  type ButtonVariant,
 } from '#/components/Button'
 import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
 import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
@@ -27,258 +25,136 @@ export function Buttons() {
           'negative_secondary',
         ].map(color => (
           <View key={color} style={[a.gap_md, a.align_start]}>
-            {['solid', 'outline', 'ghost'].map(variant => (
-              <React.Fragment key={variant}>
-                <Button
-                  variant={variant as ButtonVariant}
-                  color={color as ButtonColor}
-                  size="large"
-                  label="Click here">
-                  <ButtonText>Button</ButtonText>
-                </Button>
-                <Button
-                  disabled
-                  variant={variant as ButtonVariant}
-                  color={color as ButtonColor}
-                  size="large"
-                  label="Click here">
-                  <ButtonText>Button</ButtonText>
-                </Button>
-              </React.Fragment>
-            ))}
+            <Button
+              color={color as ButtonColor}
+              size="large"
+              label="Click here">
+              <ButtonText>Button</ButtonText>
+            </Button>
+            <Button
+              disabled
+              color={color as ButtonColor}
+              size="large"
+              label="Click here">
+              <ButtonText>Button</ButtonText>
+            </Button>
           </View>
         ))}
-
-        <View style={[a.flex_row, a.gap_md, a.align_start]}>
-          <View style={[a.gap_md, a.align_start]}>
-            {['gradient_sky', 'gradient_midnight', 'gradient_sunrise'].map(
-              name => (
-                <React.Fragment key={name}>
-                  <Button
-                    variant="gradient"
-                    color={name as ButtonColor}
-                    size="large"
-                    label="Click here">
-                    <ButtonText>Button</ButtonText>
-                  </Button>
-                  <Button
-                    disabled
-                    variant="gradient"
-                    color={name as ButtonColor}
-                    size="large"
-                    label="Click here">
-                    <ButtonText>Button</ButtonText>
-                  </Button>
-                </React.Fragment>
-              ),
-            )}
-          </View>
-        </View>
       </View>
 
       <View style={[a.flex_wrap, a.gap_md, a.align_start]}>
-        <Button variant="solid" color="primary" size="large" label="Link out">
+        <Button color="primary" size="large" label="Link out">
           <ButtonText>Button</ButtonText>
         </Button>
-        <Button variant="solid" color="primary" size="large" label="Link out">
+        <Button color="primary" size="large" label="Link out">
           <ButtonText>Button</ButtonText>
           <ButtonIcon icon={Globe} position="right" />
         </Button>
 
-        <Button variant="solid" color="primary" size="small" label="Link out">
+        <Button color="primary" size="small" label="Link out">
           <ButtonText>Button</ButtonText>
         </Button>
-        <Button variant="solid" color="primary" size="small" label="Link out">
+        <Button color="primary" size="small" label="Link out">
           <ButtonText>Button</ButtonText>
           <ButtonIcon icon={Globe} position="right" />
         </Button>
 
-        <Button variant="solid" color="primary" size="tiny" label="Link out">
+        <Button color="primary" size="tiny" label="Link out">
           <ButtonIcon icon={Globe} position="left" />
           <ButtonText>Button</ButtonText>
         </Button>
       </View>
 
       <View style={[a.flex_row, a.gap_md, a.align_center]}>
-        <Button variant="solid" color="primary" size="large" label="Link out">
+        <Button color="primary" size="large" label="Link out">
           <ButtonText>Button</ButtonText>
         </Button>
-        <Button variant="solid" color="primary" size="large" label="Link out">
+        <Button color="primary" size="large" label="Link out">
           <ButtonText>Button</ButtonText>
           <ButtonIcon icon={Globe} position="right" />
         </Button>
-        <Button variant="solid" color="primary" size="large" label="Link out">
+        <Button color="primary" size="large" label="Link out">
           <ButtonText>Button</ButtonText>
           <ButtonIcon icon={Globe} position="right" size="lg" />
         </Button>
-        <Button
-          variant="solid"
-          color="primary"
-          size="large"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="large" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="solid"
-          color="primary"
-          size="large"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="large" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} size="lg" />
         </Button>
       </View>
 
       <View style={[a.flex_row, a.gap_md, a.align_center]}>
-        <Button variant="solid" color="primary" size="small" label="Link out">
+        <Button color="primary" size="small" label="Link out">
           <ButtonText>Button</ButtonText>
         </Button>
-        <Button variant="solid" color="primary" size="small" label="Link out">
+        <Button color="primary" size="small" label="Link out">
           <ButtonText>Button</ButtonText>
           <ButtonIcon icon={Globe} position="right" />
         </Button>
-        <Button
-          variant="solid"
-          color="primary"
-          size="small"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="small" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="solid"
-          color="primary"
-          size="small"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="small" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} size="lg" />
         </Button>
       </View>
 
       <View style={[a.flex_row, a.gap_md, a.align_center]}>
-        <Button variant="solid" color="primary" size="tiny" label="Link out">
+        <Button color="primary" size="tiny" label="Link out">
           <ButtonText>Button</ButtonText>
         </Button>
-        <Button variant="solid" color="primary" size="tiny" label="Link out">
+        <Button color="primary" size="tiny" label="Link out">
           <ButtonText>Button</ButtonText>
           <ButtonIcon icon={Globe} position="right" />
         </Button>
-        <Button
-          variant="solid"
-          color="primary"
-          size="tiny"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="tiny" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="solid"
-          color="primary"
-          size="tiny"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="tiny" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} size="md" />
         </Button>
       </View>
 
       <View style={[a.flex_row, a.gap_md, a.align_center]}>
-        <Button
-          variant="solid"
-          color="primary"
-          size="large"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="large" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="gradient"
-          color="gradient_sunset"
-          size="small"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="small" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="gradient"
-          color="gradient_sunset"
-          size="tiny"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="tiny" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="outline"
-          color="primary"
-          size="large"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="large" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="ghost"
-          color="primary"
-          size="small"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="small" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="ghost"
-          color="primary"
-          size="tiny"
-          shape="round"
-          label="Link out">
+        <Button color="primary" size="tiny" shape="round" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
       </View>
 
       <View style={[a.flex_row, a.gap_md, a.align_start]}>
-        <Button
-          variant="solid"
-          color="primary"
-          size="large"
-          shape="square"
-          label="Link out">
+        <Button color="primary" size="large" shape="square" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="gradient"
-          color="gradient_sunset"
-          size="small"
-          shape="square"
-          label="Link out">
+        <Button color="primary" size="small" shape="square" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="gradient"
-          color="gradient_sunset"
-          size="tiny"
-          shape="square"
-          label="Link out">
+        <Button color="primary" size="tiny" shape="square" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="outline"
-          color="primary"
-          size="large"
-          shape="square"
-          label="Link out">
+        <Button color="primary" size="large" shape="square" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="ghost"
-          color="primary"
-          size="small"
-          shape="square"
-          label="Link out">
+        <Button color="primary" size="small" shape="square" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
-        <Button
-          variant="ghost"
-          color="primary"
-          size="tiny"
-          shape="square"
-          label="Link out">
+        <Button color="primary" size="tiny" shape="square" label="Link out">
           <ButtonIcon icon={ChevronLeft} />
         </Button>
       </View>
diff --git a/src/view/screens/Storybook/Forms.tsx b/src/view/screens/Storybook/Forms.tsx
index ad130b376..45a1d9aa0 100644
--- a/src/view/screens/Storybook/Forms.tsx
+++ b/src/view/screens/Storybook/Forms.tsx
@@ -1,5 +1,5 @@
 import React from 'react'
-import {TextInput, View} from 'react-native'
+import {type TextInput, View} from 'react-native'
 
 import {atoms as a} from '#/alf'
 import {Button, ButtonText} from '#/components/Button'
@@ -216,8 +216,8 @@ export function Forms() {
       </View>
 
       <Button
-        variant="gradient"
-        color="gradient_nordic"
+        variant="solid"
+        color="primary"
         size="small"
         label="Reset all toggles"
         onPress={() => {
diff --git a/src/view/screens/Storybook/index.tsx b/src/view/screens/Storybook/index.tsx
index 40ef79cca..1151d5a3c 100644
--- a/src/view/screens/Storybook/index.tsx
+++ b/src/view/screens/Storybook/index.tsx
@@ -52,7 +52,6 @@ function StorybookInner() {
           <>
             <View style={[a.flex_row, a.align_start, a.gap_md]}>
               <Button
-                variant="outline"
                 color="primary"
                 size="small"
                 label='Set theme to "system"'
@@ -60,7 +59,6 @@ function StorybookInner() {
                 <ButtonText>System</ButtonText>
               </Button>
               <Button
-                variant="solid"
                 color="secondary"
                 size="small"
                 label='Set theme to "light"'
@@ -68,7 +66,6 @@ function StorybookInner() {
                 <ButtonText>Light</ButtonText>
               </Button>
               <Button
-                variant="solid"
                 color="secondary"
                 size="small"
                 label='Set theme to "dim"'
@@ -79,7 +76,6 @@ function StorybookInner() {
                 <ButtonText>Dim</ButtonText>
               </Button>
               <Button
-                variant="solid"
                 color="secondary"
                 size="small"
                 label='Set theme to "dark"'
@@ -94,7 +90,6 @@ function StorybookInner() {
             <Toasts />
 
             <Button
-              variant="solid"
               color="primary"
               size="small"
               onPress={() => navigation.navigate('SharedPreferencesTester')}
@@ -128,7 +123,6 @@ function StorybookInner() {
             <Settings />
 
             <Button
-              variant="solid"
               color="primary"
               size="large"
               label="Switch to Contained List"
@@ -139,7 +133,6 @@ function StorybookInner() {
         ) : (
           <>
             <Button
-              variant="solid"
               color="primary"
               size="large"
               label="Switch to Storybook"