App Store Button
A flexible and reusable app store button component that accepts any icon (SVG/ReactNode) and custom text content. Perfect for linking to Google Play, App Store, Galaxy Store, or any other app marketplace. Built with accessibility in mind and styled using Ocean UI design tokens.
Example
import { AppStoreButton } from "@/components/library/react";
import { GooglePlayIcon } from "@/components/icons/google-play-icon";
import { AppStoreIcon } from "@/components/icons/app-store-icon";
export default function AppStoreButtonDemo() {
return (
<div className="flex flex-col gap-6">
{/* Google Play Buttons - Two sizes */}
<div className="flex flex-wrap items-center gap-4">
<AppStoreButton
icon={<GooglePlayIcon />}
topText="GET IT ON"
bottomText="Google Play"
href="https://play.google.com"
variant="default"
size="lg"
/>
<AppStoreButton
icon={<GooglePlayIcon />}
topText="GET IT ON"
bottomText="Google Play"
href="https://play.google.com"
variant="default"
size="md"
/>
</div>
{/* App Store Buttons - Two sizes */}
<div className="flex flex-wrap items-center gap-4">
<AppStoreButton
icon={<AppStoreIcon />}
topText="Download on the"
bottomText="App Store"
href="https://apps.apple.com"
variant="default"
size="lg"
/>
<AppStoreButton
icon={<AppStoreIcon />}
topText="Download on the"
bottomText="App Store"
href="https://apps.apple.com"
variant="default"
size="md"
/>
</div>
</div>
);
}
import { AppStoreButton } from "@/components/ui/app-store-button";
import { GooglePlayIcon } from "../../../components/icons/google-play-icon";
import { AppStoreIcon } from "../../../components/icons/app-store-icon";
export default function AppStoreButtonDemo() {
return (
<div class="flex flex-col gap-6">
{/* Google Play Buttons - Two sizes */}
<div class="flex flex-wrap items-center gap-4">
<AppStoreButton
icon={<GooglePlayIcon />}
topText="GET IT ON"
bottomText="Google Play"
href="https://play.google.com"
variant="default"
size="lg"
/>
<AppStoreButton
icon={<GooglePlayIcon />}
topText="GET IT ON"
bottomText="Google Play"
href="https://play.google.com"
variant="default"
size="md"
/>
</div>
{/* App Store Buttons - Two sizes */}
<div class="flex flex-wrap items-center gap-4">
<AppStoreButton
icon={<AppStoreIcon />}
topText="Download on the"
bottomText="App Store"
href="https://apps.apple.com"
variant="default"
size="lg"
/>
<AppStoreButton
icon={<AppStoreIcon />}
topText="Download on the"
bottomText="App Store"
href="https://apps.apple.com"
variant="default"
size="md"
/>
</div>
</div>
);
}
How to install and use
Complete the manual installation setup
If you haven't already completed the first 4 steps of the manual installation guide, please do so before continuing to install these components.
Create a app-store-button.tsx file and paste the following code into it.
import { cva, type VariantProps } from "class-variance-authority";
import type { AnchorHTMLAttributes, ReactNode } from "react";
import { cn } from "@/lib/utils";
const appStoreButtonVariants = cva(
"inline-flex items-center gap-2.5 px-3 rounded-[7px] transition-all outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-black text-white hover:bg-black/90 dark:bg-white dark:text-black dark:hover:bg-white/90",
outline:
"bg-transparent border border-border text-foreground hover:bg-muted ring-1 ring-inset",
},
size: {
md: "min-w-[135px] h-10",
lg: "min-w-[149px] h-11",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
},
);
export interface AppStoreButtonProps
extends
AnchorHTMLAttributes<HTMLAnchorElement>,
VariantProps<typeof appStoreButtonVariants> {
icon: ReactNode;
topText: string;
bottomText: string;
}
function AppStoreButton({
icon,
topText,
bottomText,
variant,
size,
className,
"aria-label": ariaLabel,
...props
}: AppStoreButtonProps) {
// Generate aria-label from text if not provided
const label = ariaLabel || `${topText} ${bottomText}`;
return (
<a
aria-label={label}
className={cn(appStoreButtonVariants({ variant, size, className }))}
{...props}
>
{/* Icon wrapper */}
<div
className={cn(
"shrink-0 flex items-center justify-center [&_svg]:fill-current",
size === "md"
? "w-5 h-5 [&_svg]:w-5 [&_svg]:h-5"
: "w-6 h-6 [&_svg]:w-6 [&_svg]:h-6",
)}
>
{icon}
</div>
{/* Text wrapper */}
<div className="flex flex-col items-start justify-center leading-tight">
<span
className={cn(
"font-normal uppercase tracking-wide",
size === "md"
? "text-[7px] leading-tight"
: "text-[8px] leading-tight",
)}
>
{topText}
</span>
<span
className={cn(
"font-semibold leading-none",
size === "md" ? "text-sm" : "text-base",
)}
>
{bottomText}
</span>
</div>
</a>
);
}
export { AppStoreButton, appStoreButtonVariants };
import { cva, type VariantProps } from "class-variance-authority";
import type { ComponentProps, ParentComponent, JSX } from "solid-js";
import { splitProps } from "solid-js";
import { cn } from "@/lib/utils";
const appStoreButtonVariants = cva(
"inline-flex items-center gap-2.5 px-3 rounded-[7px] transition-all outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-black text-white hover:bg-black/90 dark:bg-white dark:text-black dark:hover:bg-white/90",
outline:
"bg-transparent border border-border text-foreground hover:bg-muted ring-1 ring-inset",
},
size: {
md: "min-w-[135px] h-10",
lg: "min-w-[149px] h-11",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
},
);
export interface AppStoreButtonProps
extends ComponentProps<"a">,
VariantProps<typeof appStoreButtonVariants> {
icon: JSX.Element;
topText: string;
bottomText: string;
}
export const AppStoreButton: ParentComponent<AppStoreButtonProps> = (props) => {
const [local, rest] = splitProps(
props,
["icon", "topText", "bottomText", "variant", "size", "class", "aria-label"],
);
// Generate aria-label from text if not provided
const label = local["aria-label"] || `${local.topText} ${local.bottomText}`;
return (
<a
aria-label={label}
class={cn(
appStoreButtonVariants({
variant: local.variant,
size: local.size,
}),
local.class,
)}
{...rest}
>
{/* Icon wrapper */}
<div
class={cn(
"shrink-0 flex items-center justify-center [&_svg]:fill-current",
local.size === "md"
? "w-5 h-5 [&_svg]:w-5 [&_svg]:h-5"
: "w-6 h-6 [&_svg]:w-6 [&_svg]:h-6",
)}
>
{local.icon}
</div>
{/* Text wrapper */}
<div class="flex flex-col items-start justify-center leading-tight">
<span
class={cn(
"font-normal uppercase tracking-wide",
local.size === "md"
? "text-[7px] leading-tight"
: "text-[8px] leading-tight",
)}
>
{local.topText}
</span>
<span
class={cn(
"font-semibold leading-none",
local.size === "md" ? "text-sm" : "text-base",
)}
>
{local.bottomText}
</span>
</div>
</a>
);
};
export { appStoreButtonVariants };
import { cva, type VariantProps } from "class-variance-authority";
import type { AnchorHTMLAttributes, ReactNode } from "react";
import { cn } from "@/lib/utils";
const appStoreButtonVariants = cva(
"inline-flex items-center gap-2.5 px-3 rounded-[7px] transition-all outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-black text-white hover:bg-black/90 dark:bg-white dark:text-black dark:hover:bg-white/90",
outline:
"bg-transparent border border-border text-foreground hover:bg-muted ring-1 ring-inset",
},
size: {
md: "min-w-[135px] h-10",
lg: "min-w-[149px] h-11",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
},
);
export interface AppStoreButtonProps
extends
AnchorHTMLAttributes<HTMLAnchorElement>,
VariantProps<typeof appStoreButtonVariants> {
icon: ReactNode;
topText: string;
bottomText: string;
}
function AppStoreButton({
icon,
topText,
bottomText,
variant,
size,
className,
"aria-label": ariaLabel,
...props
}: AppStoreButtonProps) {
// Generate aria-label from text if not provided
const label = ariaLabel || `${topText} ${bottomText}`;
return (
<a
aria-label={label}
className={cn(appStoreButtonVariants({ variant, size, className }))}
{...props}
>
{/* Icon wrapper */}
<div
className={cn(
"shrink-0 flex items-center justify-center [&_svg]:fill-current",
size === "md"
? "w-5 h-5 [&_svg]:w-5 [&_svg]:h-5"
: "w-6 h-6 [&_svg]:w-6 [&_svg]:h-6",
)}
>
{icon}
</div>
{/* Text wrapper */}
<div className="flex flex-col items-start justify-center leading-tight">
<span
className={cn(
"font-normal uppercase tracking-wide",
size === "md"
? "text-[7px] leading-tight"
: "text-[8px] leading-tight",
)}
>
{topText}
</span>
<span
className={cn(
"font-semibold leading-none",
size === "md" ? "text-sm" : "text-base",
)}
>
{bottomText}
</span>
</div>
</a>
);
}
export { AppStoreButton, appStoreButtonVariants };
import { cva, type VariantProps } from "class-variance-authority";
import type { AnchorHTMLAttributes, ReactNode } from "react";
import { cn } from "@/lib/utils";
const appStoreButtonVariants = cva(
"inline-flex items-center gap-2.5 px-3 rounded-[7px] transition-all outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-black text-white hover:bg-black/90 dark:bg-white dark:text-black dark:hover:bg-white/90",
outline:
"bg-transparent border border-border text-foreground hover:bg-muted ring-1 ring-inset",
},
size: {
md: "min-w-[135px] h-10",
lg: "min-w-[149px] h-11",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
},
);
export interface AppStoreButtonProps
extends
AnchorHTMLAttributes<HTMLAnchorElement>,
VariantProps<typeof appStoreButtonVariants> {
icon: ReactNode;
topText: string;
bottomText: string;
}
function AppStoreButton({
icon,
topText,
bottomText,
variant,
size,
className,
"aria-label": ariaLabel,
...props
}: AppStoreButtonProps) {
// Generate aria-label from text if not provided
const label = ariaLabel || `${topText} ${bottomText}`;
return (
<a
aria-label={label}
className={cn(appStoreButtonVariants({ variant, size, className }))}
{...props}
>
{/* Icon wrapper */}
<div
className={cn(
"shrink-0 flex items-center justify-center [&_svg]:fill-current",
size === "md"
? "w-5 h-5 [&_svg]:w-5 [&_svg]:h-5"
: "w-6 h-6 [&_svg]:w-6 [&_svg]:h-6",
)}
>
{icon}
</div>
{/* Text wrapper */}
<div className="flex flex-col items-start justify-center leading-tight">
<span
className={cn(
"font-normal uppercase tracking-wide",
size === "md"
? "text-[7px] leading-tight"
: "text-[8px] leading-tight",
)}
>
{topText}
</span>
<span
className={cn(
"font-semibold leading-none",
size === "md" ? "text-sm" : "text-base",
)}
>
{bottomText}
</span>
</div>
</a>
);
}
export { AppStoreButton, appStoreButtonVariants };
Finally, Choose any example you like and add it to your project.
For instance, create a new file at components/shared/{example-component-name}.tsx, paste the example code into that file, and then import and use the component wherever you need it in your application.
The AppStoreButton component is designed as a flexible link component (<a> tag) for app store downloads. It accepts any icon as a ReactNode, allowing you to use custom SVG icons, icon libraries, or any React component. The component automatically generates an accessible aria-label from the text content if not provided.
Overview
A flexible button component for app store download links. Pass any icon and customize the two-line text layout for maximum flexibility. Perfect for Google Play, App Store, Galaxy Store, or any custom app marketplace.
Variants
The AppStoreButton component supports two visual variants:
- default: Black background with white text (default style) -
variant="default"or no variant prop - outline: Transparent background with border, adapts to theme -
variant="outline"
Sizes
The AppStoreButton component supports two sizes:
- md: Medium button (
size="md") - height: 40px, min-width: 135px - default - lg: Large button (
size="lg") - height: 44px, min-width: 149px
Features
- Flexible Icons: Accept any ReactNode as icon (SVG components, images, custom components)
- Two-Line Text: Separate
topTextandbottomTextprops for clear layout - Multiple Variants: Default (black) and outline styles
- Size Control: Use
sizeprop to control button dimensions - Accessibility: Auto-generates
aria-labelfrom text content - Link Component: Uses
<a>tag for proper link semantics - Theme Support: Outline variant adapts to light/dark theme
- Customizable: All styles can be overridden via
classNameprop
Select Examples
Outline Variant
Outline variant buttons use variant="outline" to display a transparent background with border. Perfect for light backgrounds or when you want a more subtle appearance.
Props used: variant="outline"
import { AppStoreButton } from "@/components/library/react";
import { GooglePlayIcon } from "@/components/icons/google-play-icon";
import { AppStoreIcon } from "@/components/icons/app-store-icon";
export default function AppStoreButtonOutline() {
return (
<div className="flex flex-col gap-6">
{/* Google Play Buttons - Two sizes */}
<div className="flex flex-wrap items-center gap-4">
<AppStoreButton
icon={<GooglePlayIcon />}
topText="GET IT ON"
bottomText="Google Play"
href="https://play.google.com"
variant="outline"
size="lg"
/>
<AppStoreButton
icon={<GooglePlayIcon />}
topText="GET IT ON"
bottomText="Google Play"
href="https://play.google.com"
variant="outline"
size="md"
/>
</div>
{/* App Store Buttons - Two sizes */}
<div className="flex flex-wrap items-center gap-4">
<AppStoreButton
icon={<AppStoreIcon />}
topText="Download on the"
bottomText="App Store"
href="https://apps.apple.com"
variant="outline"
size="lg"
/>
<AppStoreButton
icon={<AppStoreIcon />}
topText="Download on the"
bottomText="App Store"
href="https://apps.apple.com"
variant="outline"
size="md"
/>
</div>
</div>
);
}
import { AppStoreButton } from "@/components/ui/app-store-button";
import { GooglePlayIcon } from "../../../components/icons/google-play-icon";
import { AppStoreIcon } from "../../../components/icons/app-store-icon";
export default function AppStoreButtonOutline() {
return (
<div class="flex flex-col gap-6">
{/* Google Play Buttons - Two sizes */}
<div class="flex flex-wrap items-center gap-4">
<AppStoreButton
icon={<GooglePlayIcon />}
topText="GET IT ON"
bottomText="Google Play"
href="https://play.google.com"
variant="outline"
size="lg"
/>
<AppStoreButton
icon={<GooglePlayIcon />}
topText="GET IT ON"
bottomText="Google Play"
href="https://play.google.com"
variant="outline"
size="md"
/>
</div>
{/* App Store Buttons - Two sizes */}
<div class="flex flex-wrap items-center gap-4">
<AppStoreButton
icon={<AppStoreIcon />}
topText="Download on the"
bottomText="App Store"
href="https://apps.apple.com"
variant="outline"
size="lg"
/>
<AppStoreButton
icon={<AppStoreIcon />}
topText="Download on the"
bottomText="App Store"
href="https://apps.apple.com"
variant="outline"
size="md"
/>
</div>
</div>
);
}
Large Size
Large buttons use size="lg" for more prominent display. Ideal for hero sections or when you want maximum visibility.
Props used: size="lg"
import { AppStoreButton } from "@/components/library/react";
import { GooglePlayIcon } from "@/components/icons/google-play-icon";
import { AppStoreIcon } from "@/components/icons/app-store-icon";
export default function AppStoreButtonLarge() {
return (
<div className="flex flex-wrap gap-4">
<AppStoreButton
icon={<GooglePlayIcon />}
topText="GET IT ON"
bottomText="Google Play"
href="https://play.google.com"
variant="default"
size="lg"
/>
<AppStoreButton
icon={<AppStoreIcon />}
topText="Download on the"
bottomText="App Store"
href="https://apps.apple.com"
variant="default"
size="lg"
/>
</div>
);
}
import { AppStoreButton } from "@/components/ui/app-store-button";
import { GooglePlayIcon } from "../../../components/icons/google-play-icon";
import { AppStoreIcon } from "../../../components/icons/app-store-icon";
export default function AppStoreButtonLarge() {
return (
<div class="flex flex-wrap gap-4">
<AppStoreButton
icon={<GooglePlayIcon />}
topText="GET IT ON"
bottomText="Google Play"
href="https://play.google.com"
variant="default"
size="lg"
/>
<AppStoreButton
icon={<AppStoreIcon />}
topText="Download on the"
bottomText="App Store"
href="https://apps.apple.com"
variant="default"
size="lg"
/>
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
icon | ReactNode | required | The icon to display (any ReactNode) |
topText | string | required | Smaller text displayed on top |
bottomText | string | required | Larger, bolder text displayed on bottom |
variant | "default" | "outline" | "default" | Visual style variant |
size | "md" | "lg" | "md" | Button size |
href | string | - | Link URL (standard anchor prop) |
aria-label | string | Auto-generated | Accessible label (auto-generated from text) |
className | string | - | Additional CSS classes |
Usage with Custom Icons
The component accepts any ReactNode as an icon, making it extremely flexible:
import { AppStoreButton } from "@/components/library/react";
import { CustomIcon } from "@/components/icons/custom-icon";
<AppStoreButton
icon={<CustomIcon />}
topText="Available on"
bottomText="Custom Store"
href="https://example.com"
/>Built-in Icons
Ocean UI provides icon components for common app stores:
GooglePlayIcon- Colorful Google Play iconAppStoreIcon- Apple App Store icon
These can be imported from @/components/icons/google-play-icon and @/components/icons/app-store-icon.