Nasaq

InstallButton

The install call to action for an app; controlled through four states, with built-in English and Arabic labels, and a quiet "Open" once installed.

PreviewOpen ↗

Code

import { InstallButton, type InstallState } from "@fadymondy/nasaq/web";import { useState } from "react";export function InstallZekra() {  const [state, setState] = useState<InstallState>("available");  function install() {    setState("installing");    setTimeout(() => setState("installed"), 1500);  }  return <InstallButton appName="Zekra" state={state} onInstall={install} onOpen={() => {}} />;}

Apps & Platforms · beta

Live examples and controls: InstallButton in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/install-button.json

A button that tracks an app through its lifecycle in a workspace: available, installing, installed, update. It is controlled: your code owns state and moves it forward. Once installed it becomes a quiet "Open" button, so an app the user already owns does not compete with apps still to install. It is built on Button.

When to use

  • The install, get, open or update action on a store card, listing or app detail page.

When not to use

  • Any other action: use Button.
  • Subscribing to a plan: use the call to action in PlanCard.
  • Long-running work outside install: use Button with loading.

Import

import { InstallButton, type InstallButtonProps, type InstallState } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"

Quick start

import { InstallButton, type InstallState } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function InstallZekra() {
  const [state, setState] = useState<InstallState>("available");

  function install() {
    setState("installing");
    setTimeout(() => setState("installed"), 1500);
  }

  return <InstallButton appName="Zekra" state={state} onInstall={install} onOpen={() => {}} />;
}

Anatomy

InstallButton           data-slot="install-button", data-state="<state>"   <Button>
├─ spinner              while state="installing" (Button loading)
├─ check icon           only when installed, aria-hidden
└─ label                Install | Get | Update | Open

API

InstallButton

InstallButtonProps extends Omit<ButtonProps, "children" | "onClick" | "loading">. Other Button props (such as disabled, className, type) are forwarded.

PropTypeDefaultDescription
appNamestringrequiredThe app's name, appended to the accessible name: "Install Mahaam".
state?InstallState"available"Where the app is in its lifecycle for this workspace.
free?booleanfalseFree apps say "Get" instead of "Install".
onInstall?() => voidnoneCalled on click when state is available or installing.
onOpen?() => voidnoneCalled on click when state is installed.
onUpdate?() => voidnoneCalled on click when state is update.
labels?Partial<{ install: string; get: string; open: string; update: string }>built-inOverride the English or Arabic labels.
variant?ButtonProps["variant"]"secondary"Button variant. Ignored when installed (always "ghost").
size?ButtonProps["size"]"sm"Button size.

InstallState

"available" | "installing" | "installed" | "update"

StateRendersClick calls
available"Install" (or "Get" when free)onInstall
installingSpinner, disabled, aria-busynone (disabled)
installedGhost "Open" with a check icononOpen
update"Update"onUpdate

Examples

Free app

import { InstallButton } from "@fadymondy/nasaq/web";

export function GetApp() {
  return <InstallButton appName="Moharrik" free onInstall={() => {}} />;
}

Arabic with a custom label

import { InstallButton, NasaqProvider } from "@fadymondy/nasaq/web";

export function ArabicInstall() {
  return (
    <NasaqProvider locale="ar" dir="rtl">
      <InstallButton appName="حوسبة" state="available" onInstall={() => {}} />
      <InstallButton appName="حوسبة" state="update" labels={{ update: "تحديث متاح" }} onUpdate={() => {}} />
    </NasaqProvider>
  );
}

Accessibility

KeyAction
Enter / SpaceActivates the current action
TabMoves focus to and from the button
  • It is a native button. The accessible name is "<label> <appName>" ("Install Zekra"), so several install buttons on one page are distinguishable.
  • While installing the button is disabled with aria-busy, but stays focusable so focus is not lost.
  • The check icon is aria-hidden.
  • Localise appName; the labels are built in for English and Arabic.

RTL & i18n

  • Built-in labels in English and Arabic (تثبيت, احصل عليه, فتح, تحديث), chosen by the provider locale (English without a provider). Override any with labels.
  • Spinner and check icon sit on the inline start through the button's flex layout, so they mirror in RTL.

Styling & tokens

  • Inherits Button styling. The check icon uses text-nq-success-text.
  • Target with [data-slot=install-button] or [data-state=installed].
  • Extend with className. Pass variant="primary" for the single main action on a page.

Do / Don't

  • Do own state in your code and move it as the install progresses.
  • Do pass appName so the button has a distinct accessible name.
  • Do use free for apps that cost nothing.
  • Don't make an installed app's button loud; it stays a quiet "Open".
  • Don't use more than one primary install button in a view.

Lab

https://docs.nasaqui.com/?path=/docs/components-apps-platforms-install-button--docs

On this page