# iOS Get Button

> App Store style GET button that turns into a ring progress indicator and becomes OPEN when finished.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/components/ios-get-button
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/ios-get-button.json
```

Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Dependencies

No extra dependencies.

## Usage

```tsx title="components/ios-get-button-demo.tsx"
import { IOSGetButton } from "@/components/ios-get-button"

export default function IOSGetButtonDemo() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4 rounded-2xl bg-white p-6 dark:bg-neutral-900">
      {[
        ["Notes+", "Capture ideas fast"],
        ["Weather Pro", "Forecasts you can trust"],
      ].map(([name, tagline], index) => (
        <div key={name} className="flex items-center gap-3">
          <div
            className={
              index ? "size-14 rounded-[14px] bg-gradient-to-br from-cyan-400 to-blue-600" : "size-14 rounded-[14px] bg-gradient-to-br from-amber-300 to-orange-500"
            }
          />
          <div className="min-w-0 flex-1">
            <p className="truncate text-[17px] font-semibold">{name}</p>
            <p className="truncate text-[13px] text-muted-foreground">{tagline}</p>
          </div>
          <IOSGetButton duration={1800 + index * 600} />
        </div>
      ))}
    </div>
  )
}
```

## Source

```tsx title="components/ios-get-button.tsx"
"use client"

import * as React from "react"

import { cn } from "@/lib/utils"

const keyframes = `@keyframes ios-get-button-ring{to{stroke-dashoffset:0}}`

export interface IOSGetButtonProps extends Omit<React.ComponentProps<"button">, "children"> {
  label?: string
  openLabel?: string
  /** Fake download time in ms */
  duration?: number
  onOpen?: () => void
}

type Phase = "idle" | "loading" | "done"

export function IOSGetButton({
  label = "GET",
  openLabel = "OPEN",
  duration = 1600,
  onOpen,
  onClick,
  className,
  ...props
}: IOSGetButtonProps) {
  const [phase, setPhase] = React.useState<Phase>("idle")
  const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null)

  React.useEffect(
    () => () => {
      if (timer.current) clearTimeout(timer.current)
    },
    []
  )

  return (
    <button
      type="button"
      aria-label={phase === "loading" ? "Downloading" : phase === "done" ? openLabel : label}
      onClick={(event) => {
        onClick?.(event)
        if (phase === "idle") {
          setPhase("loading")
          timer.current = setTimeout(() => setPhase("done"), duration)
        } else if (phase === "done") {
          onOpen?.()
        }
      }}
      className={cn(
        "relative inline-flex h-8 min-w-[74px] items-center justify-center rounded-full px-5 text-[15px] font-bold text-[#0064d2] select-none dark:text-[#6cb2ff]",
        "bg-black/[0.06] transition-[transform,opacity,background-color] duration-150 active:scale-95 active:opacity-60 dark:bg-white/15",
        "focus-visible:ring-[3px] focus-visible:ring-[#007aff]/40 focus-visible:outline-none",
        className
      )}
      {...props}
    >
      <style href="ios-get-button" precedence="default">
        {keyframes}
      </style>
      {phase === "loading" ? (
        <svg viewBox="0 0 24 24" className="size-6 -rotate-90" aria-hidden>
          <circle cx="12" cy="12" r="9" fill="none" strokeWidth="2.5" className="stroke-black/15 dark:stroke-white/25" />
          <circle
            cx="12"
            cy="12"
            r="9"
            fill="none"
            strokeWidth="2.5"
            strokeLinecap="round"
            strokeDasharray="56.55"
            strokeDashoffset="56.55"
            stroke="#007aff"
            style={{ animation: `ios-get-button-ring ${duration}ms linear forwards` }}
          />
          <rect x="9.5" y="9.5" width="5" height="5" rx="1.2" fill="#007aff" />
        </svg>
      ) : phase === "done" ? (
        openLabel
      ) : (
        label
      )}
    </button>
  )
}
```
