# ChromeExtensionInstall

> Three-step install flow for a Chrome extension (add from the Web Store, pin, sign in) with a detected state, re-check, sign-in action and a notice for unsupported browsers.

Source: https://docs.nasaqui.com/components/chrome-extension-install

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/chrome-extension-install.json
```

Walks someone through installing your browser extension: open the Web Store, pin it, sign in. It shows
whether the page found the extension, marks finished steps and keeps the next one in front. Presentational:
you tell it what was detected.

## When to use

- Onboarding for a product that has a companion extension.

## When not to use

- Extensions for other stores (Firefox, Safari): pass your own `storeUrl` and `supported`, or build a separate flow.

## Import

```tsx
import { ChromeExtensionInstall } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { ChromeExtensionInstall } from "@fadymondy/nasaq/web";

declare const state: { installed: boolean; signedIn: boolean };
declare const ext: { ping(): Promise<void>; signIn(): Promise<void> };

export function Install() {
  return (
    <ChromeExtensionInstall
      storeUrl="https://chromewebstore.google.com/detail/your-extension"
      installed={state.installed}
      signedIn={state.signedIn}
      onCheck={() => ext.ping()}
      onSignIn={() => ext.signIn()}
    />
  );
}
```

## Anatomy

```
ChromeExtensionInstall          data-slot="chrome-extension-install" (data-state="missing|installed|ready")
├─ unsupported Alert            when `supported` is false
├─ detected row                 data-slot="extension-detected": Status + Check again
├─ steps (ol)                   data-slot="extension-step" (data-state="done|current|todo")
│  ├─ 1 Add from the store      link button (data-slot="extension-store-link")
│  ├─ 2 Pin                     "I pinned it" (the page cannot detect pinning)
│  └─ 3 Sign in                 button, error line
└─ ready Alert                  when all three are done
```

## API

**ChromeExtensionInstall**: every `div` prop except `children`, plus:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `storeUrl` | `string` | required | The Web Store page. |
| `installed` | `boolean` | required | Whether the page found the extension. |
| `version` | `string` | | Shown when detected. |
| `signedIn` | `boolean` | `false` | Whether the extension is signed in. |
| `pinned`, `defaultPinned`, `onPinnedChange` | | uncontrolled | The person confirms pinning. |
| `onCheck` | `() => Promise<void>` | | Shows Check again. Update `installed` afterwards. |
| `onSignIn` | `() => Promise<void \| { error? }>` | | Runs the sign-in step. |
| `supported` | `boolean` | `true` | Show a notice for browsers that cannot install it. |
| `storeButton` | `ReactNode` | text | Replace the button content, for example a badge you are licensed to use. |
| `labels` | `Partial<ChromeExtensionInstallLabels>` | | Override any string. |

## Examples

**Unsupported browser**

```tsx
import { ChromeExtensionInstall } from "@fadymondy/nasaq/web";

export const Firefox = () => <ChromeExtensionInstall storeUrl="https://example.com" installed={false} supported={false} />;
```

## Accessibility

- Steps are an ordered list; the current step has `aria-current="step"` and each has screen-reader text ("Step 2 of 3, current").
- The detected row is `role="status"`. Sign-in errors are `role="alert"`.

## RTL & i18n

- English and Arabic strings follow the Nasaq locale; the step rail mirrors.

## Styling & tokens

- Built on `Card`, `Button`, `Status`, `Alert` and `--nq-*` tokens.
- Target `[data-slot="extension-step"][data-state="current"]`.

## Do / Don't

- Do detect the extension with a message or a marker the extension adds to the page.
- Don't claim you can detect pinning: ask.
- The Chrome and Web Store marks are Google's. This component uses text; use Google's badge only under its brand rules, through `storeButton`.

## Related

- [`WhatsappQrConnect`](https://docs.nasaqui.com/components/whatsapp-qr-connect)
- [`IntegrationConnector`](https://docs.nasaqui.com/components/integration-connector)

## Lab

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

## Code

### React

```tsx
import { ChromeExtensionInstall } from "@fadymondy/nasaq/web";

declare const state: { installed: boolean; signedIn: boolean };
declare const ext: { ping(): Promise<void>; signIn(): Promise<void> };

export function Install() {
  return (
    <ChromeExtensionInstall
      storeUrl="https://chromewebstore.google.com/detail/your-extension"
      installed={state.installed}
      signedIn={state.signedIn}
      onCheck={() => ext.ping()}
      onSignIn={() => ext.signIn()}
    />
  );
}
```

### shadcn

```tsx
import { ChromeExtensionInstall } from "@/components/ui/chrome-extension-install";

declare const state: { installed: boolean; signedIn: boolean };
declare const ext: { ping(): Promise<void>; signIn(): Promise<void> };

export function Install() {
  return (
    <ChromeExtensionInstall
      storeUrl="https://chromewebstore.google.com/detail/your-extension"
      installed={state.installed}
      signedIn={state.signedIn}
      onCheck={() => ext.ping()}
      onSignIn={() => ext.signIn()}
    />
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { ref } from "vue";
import { NqChromeExtensionInstall } from "@fadymondy/nasaq/vue";

const installed = ref(false);
const signedIn = ref(false);
const check = async () => {
  await new Promise((r) => setTimeout(r, 600));
  installed.value = true;
};
const signIn = async () => {
  await new Promise((r) => setTimeout(r, 600));
  signedIn.value = true;
};
</script>

<template>
  <NqChromeExtensionInstall store-url="https://chromewebstore.google.com/detail/example" :installed="installed" version="1.0.0" :signed-in="signedIn" :on-check="check" :on-sign-in="signIn" />
</template>
```

### Blade

```blade
<x-nq::chrome-extension-install store-url="https://chromewebstore.google.com/detail/example" version="1.0.0" check sign-in />
```

### HTML + Alpine

```html
<div data-slot="chrome-extension-install" x-data="nqExtensionInstall(JSON.parse('{\u0022installed\u0022:false,\u0022signedIn\u0022:false,\u0022pinned\u0022:false,\u0022version\u0022:\u00221.0.0\u0022,\u0022canCheck\u0022:true,\u0022canSignIn\u0022:true,\u0022detected\u0022:\u0022Extension detected\u0022,\u0022detectedVersion\u0022:\u0022Extension detected, version {version}\u0022,\u0022signInFailed\u0022:\u0022Could not sign in. Try again.\u0022}'))" x-bind:data-state="rootState" class="flex flex-col gap-4 rounded-card border border-border bg-card py-4 text-card-foreground w-full max-w-2xl">
    <div data-slot="card-header" class="grid auto-rows-min items-start gap-1 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]"><h2 data-slot="card-title" class="text-label text-foreground">Install the browser extension</h2>
        <div data-slot="card-description" class="text-body-sm text-muted-foreground">Three steps: add it, pin it, sign in.</div></div>
    <div data-slot="card-content" class="px-4 flex flex-col gap-5"><div class="flex flex-wrap items-center justify-between gap-2 rounded-card border border-border bg-secondary px-3 py-2" data-slot="extension-detected" role="status">
            <span data-slot="status" data-tone="success" x-show="installed" style="display: none" class="inline-flex min-w-0 items-center gap-1.5 text-body-sm text-foreground">
            <svg aria-hidden="true" class="size-3.5 shrink-0 text-nq-success-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m9 12 2 2 4-4"/>
</svg>        <span class="truncate"  title="Extension detected, version 1.0.0" ><span x-text="detectedText">Extension detected, version 1.0.0</span></span>
</span>
            <span data-slot="status" data-tone="neutral" x-show="!installed" class="inline-flex min-w-0 items-center gap-1.5 text-body-sm text-foreground">
            <svg aria-hidden="true" class="size-3.5 shrink-0 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
</svg>        <span class="truncate"  title="Extension not detected yet" >Extension not detected yet</span>
</span>
                            <button data-slot="button"
     type="button"                         x-show="!installed" x-on:click="check()" x-bind:aria-busy="checking ? &#039;true&#039; : null" x-bind:data-disabled="checking ? &#039;&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        <svg data-slot="spinner" aria-hidden="true" style="display: none;" x-show="checking" class="size-4 animate-spin motion-reduce:animate-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg>                    <svg aria-hidden="true" x-show="!checking" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
  <path d="M21 3v5h-5"/>
  <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
  <path d="M8 16H3v5"/>
</svg>                    <span x-text="checking ? 'Checking\u2026' : 'Check again'">Check again</span></button>
                    </div>
        <ol aria-label="Installation steps" class="flex flex-col gap-0">
                                            <li data-slot="extension-step" x-bind:data-state="state(0)" x-bind:aria-current="state(0) === 'current' ? 'step' : null" class="relative flex gap-3 pb-5 last:pb-0">
                                            <span aria-hidden="true" class="absolute inset-y-7 start-[13px] w-px bg-border"></span>
                                        <span aria-hidden="true"
                        x-bind:class="{ 'border-nq-success/40 bg-nq-success-soft text-nq-success-text': state(0) === 'done', 'border-primary bg-primary text-primary-foreground': state(0) === 'current', 'border-border bg-card text-muted-foreground': state(0) === 'todo' }"
                        class="z-10 inline-flex size-7 shrink-0 items-center justify-center rounded-full border text-caption tabular-nums border-primary bg-primary text-primary-foreground">
                        <svg x-show="state(0) === 'done'" style="display: none" class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg>                        <span x-show="state(0) !== 'done'" >1</span>
                    </span>
                    <div class="flex min-w-0 flex-1 flex-col gap-1.5">
                        <p x-bind:class="{ 'text-muted-foreground': state(0) === 'todo', 'text-foreground': state(0) !== 'todo' }" class="text-label text-foreground">
                            <span class="sr-only" x-text="'Step {n} of {total}, {state}'.replace('{n}', 1).replace('{total}', 3).replace('{state}', { done: 'done', current: 'current', todo: 'to do' }[state(0)])">Step 1 of 3, current</span><span class="sr-only">: </span>
                            Add it from the Chrome Web Store
                        </p>
                        <p x-show="state(0) !== 'done'" class="text-body-sm text-muted-foreground" >Open the store page and choose Add to Chrome, then confirm in the pop-up.</p>
                                                <div x-show="showActions(0)" class="flex flex-wrap items-center gap-2 pt-0.5" >
                                                            <a data-slot="extension-store-link"
     href="https://chromewebstore.google.com/detail/example"                         target="_blank" rel="noreferrer" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control-sm px-2.5">
        Open the Chrome Web Store<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M15 3h6v6"/>
  <path d="M10 14 21 3"/>
  <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
</svg></a>
                                                    </div>
                                                                    </div>
                </li>
                                            <li data-slot="extension-step" x-bind:data-state="state(1)" x-bind:aria-current="state(1) === 'current' ? 'step' : null" class="relative flex gap-3 pb-5 last:pb-0">
                                            <span aria-hidden="true" class="absolute inset-y-7 start-[13px] w-px bg-border"></span>
                                        <span aria-hidden="true"
                        x-bind:class="{ 'border-nq-success/40 bg-nq-success-soft text-nq-success-text': state(1) === 'done', 'border-primary bg-primary text-primary-foreground': state(1) === 'current', 'border-border bg-card text-muted-foreground': state(1) === 'todo' }"
                        class="z-10 inline-flex size-7 shrink-0 items-center justify-center rounded-full border text-caption tabular-nums border-border bg-card text-muted-foreground">
                        <svg x-show="state(1) === 'done'" style="display: none" class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg>                        <span x-show="state(1) !== 'done'" >2</span>
                    </span>
                    <div class="flex min-w-0 flex-1 flex-col gap-1.5">
                        <p x-bind:class="{ 'text-muted-foreground': state(1) === 'todo', 'text-foreground': state(1) !== 'todo' }" class="text-label text-muted-foreground">
                            <span class="sr-only" x-text="'Step {n} of {total}, {state}'.replace('{n}', 2).replace('{total}', 3).replace('{state}', { done: 'done', current: 'current', todo: 'to do' }[state(1)])">Step 2 of 3, to do</span><span class="sr-only">: </span>
                            Pin it to the toolbar
                        </p>
                        <p x-show="state(1) !== 'done'" class="text-body-sm text-muted-foreground" >Click the puzzle-piece Extensions button next to the address bar, then the pin beside the extension. It stays one click away.</p>
                                                <div x-show="showActions(1)" class="flex flex-wrap items-center gap-2 pt-0.5"  style="display: none" >
                                                            <button data-slot="button"
     type="button"                         x-show="pinned" x-on:click="setPinned(false)" style="display: none" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        Undo</button>
                                <button data-slot="button"
     type="button"                         x-show="!pinned" x-bind:disabled="!installed || null" x-bind:data-disabled="!installed ? &#039;&#039; : null" x-on:click="setPinned(true)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control-sm px-2.5">
        I pinned it</button>
                                                    </div>
                                                    <p x-show="state(1) === 'todo' && !installed" class="text-caption text-muted-foreground" >Do the step above first.</p>
                                                                    </div>
                </li>
                                            <li data-slot="extension-step" x-bind:data-state="state(2)" x-bind:aria-current="state(2) === 'current' ? 'step' : null" class="relative flex gap-3 pb-5 last:pb-0">
                                        <span aria-hidden="true"
                        x-bind:class="{ 'border-nq-success/40 bg-nq-success-soft text-nq-success-text': state(2) === 'done', 'border-primary bg-primary text-primary-foreground': state(2) === 'current', 'border-border bg-card text-muted-foreground': state(2) === 'todo' }"
                        class="z-10 inline-flex size-7 shrink-0 items-center justify-center rounded-full border text-caption tabular-nums border-border bg-card text-muted-foreground">
                        <svg x-show="state(2) === 'done'" style="display: none" class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg>                        <span x-show="state(2) !== 'done'" >3</span>
                    </span>
                    <div class="flex min-w-0 flex-1 flex-col gap-1.5">
                        <p x-bind:class="{ 'text-muted-foreground': state(2) === 'todo', 'text-foreground': state(2) !== 'todo' }" class="text-label text-muted-foreground">
                            <span class="sr-only" x-text="'Step {n} of {total}, {state}'.replace('{n}', 3).replace('{total}', 3).replace('{state}', { done: 'done', current: 'current', todo: 'to do' }[state(2)])">Step 3 of 3, to do</span><span class="sr-only">: </span>
                            Sign in from the extension
                        </p>
                        <p x-show="state(2) !== 'done'" class="text-body-sm text-muted-foreground" >Click the extension, then Sign in. It uses your account, so nothing is typed twice.</p>
                                                    <span data-slot="status" data-tone="success" x-show="state(2) === &#039;done&#039;" style="display: none" class="inline-flex min-w-0 items-center gap-1.5 text-body-sm text-foreground">
            <svg aria-hidden="true" class="size-3.5 shrink-0 text-nq-success-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m9 12 2 2 4-4"/>
</svg>        <span class="truncate"  title="Signed in" >Signed in</span>
</span>
                                                <div x-show="showActions(2)" class="flex flex-wrap items-center gap-2 pt-0.5"  style="display: none" >
                                                            <button data-slot="button"
     type="button"                         x-show="!signedIn" x-bind:disabled="(!installed || !canSignIn) || null" x-bind:aria-busy="signing ? &#039;true&#039; : null" x-bind:data-disabled="(signing || !installed || !canSignIn) ? &#039;&#039; : null" x-on:click="signIn()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control-sm px-2.5">
        <svg data-slot="spinner" aria-hidden="true" style="display: none;" x-show="signing" class="size-4 animate-spin motion-reduce:animate-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg>Sign in</button>
                                                    </div>
                                                    <p x-show="state(2) === 'todo' && !installed" class="text-caption text-muted-foreground" >Do the step above first.</p>
                                                                            <p role="alert" x-show="error" x-text="error" class="text-caption text-nq-danger-text" style="display: none"></p>
                                            </div>
                </li>
                    </ol>
        <div data-slot="alert" data-tone="success" role="status"
        x-show="allDone" style="display: none" class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-success/30 bg-nq-success-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-success-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m9 12 2 2 4-4"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                    <div data-slot="alert-title" class="text-label text-foreground">You are all set</div>
                            <div data-slot="alert-description" class="text-body-sm text-muted-foreground">The extension is installed, pinned and signed in.</div>
            </div>
    </div></div>
</div>
```
