# BrandingProvider

> Applies a tenant's brand colours at runtime from data (an org's settings, a white-label customer) over the active brand manifest, with readable on-brand text, a derived dark step and the tenant logo and name shared through useBranding.

Source: https://docs.nasaqui.com/components/branding-provider

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/branding-provider.json
```

`NasaqProvider brand="…"` picks a brand manifest at build time. BrandingProvider is for colours that arrive
at runtime: it writes the brand variables (`--nq-brand-l/d`, `--nq-action-l/d`, `--nq-on-action-l/d`,
`--nq-accent-brand`) so every primary button, link and badge follows the tenant.

## When to use

- Multi-tenant and white-label apps where each organisation picks its colour and logo.
- A settings page that previews a colour before saving (scope it with `target`).

## When not to use

- One fixed brand: use a manifest in `NasaqProvider`.
- Recolouring a single element: use a class or a style.

## Import

```tsx
import { BrandingProvider, useBranding, applyBrand } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { BrandingProvider, NasaqProvider, useBranding } from "@fadymondy/nasaq/web";
import type { ReactNode } from "react";

declare const org: { name: string; brandColor: string | null; logoUrl: string | null };

function OrgLogo() {
  const b = useBranding();
  return b?.logoUrl ? <img src={b.logoUrl} alt={b.name ?? ""} className="h-6" /> : <span>{b?.name}</span>;
}

export function App({ children }: { children: ReactNode }) {
  return (
    <NasaqProvider>
      <BrandingProvider brand={org.brandColor ?? undefined} logoUrl={org.logoUrl} name={org.name}>
        <OrgLogo />
        {children}
      </BrandingProvider>
    </NasaqProvider>
  );
}
```

## Anatomy

BrandingProvider renders no element. On mount it calls `applyBrand(target, colors)` and removes the
variables on unmount or change. A scoped `target` (not `<html>`) also gets `data-brand="runtime"`, so the
derived roles (`--nq-brand`, `--primary`) re-resolve inside it.

## API

**BrandingProvider**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `brand` | `string` | manifest | Brand colour on light surfaces, `#RRGGBB` or `#RGB`. |
| `brandDark` | `string` | lighter step of `brand` | Brand colour on dark surfaces. |
| `action` / `actionDark` | `string` | the brand colours | Primary button fill. |
| `accent` | `string` | manifest | Featured / new accent. |
| `logoUrl` | `string \| null` | | Shared through `useBranding`. Blank becomes `null`. |
| `name` | `string` | | Tenant name, shared through `useBranding`. |
| `target` | `() => HTMLElement \| null` | `document.documentElement` | Scope the colours to one element. |

Invalid colours are skipped, so a bad value from the database falls back to the manifest.

**useBranding(): BrandingValue | null**: the colours, `logoUrl` and `name`; null outside a provider.

**applyBrand(root, colors): () => void**: the same, without React. Returns a cleanup.

**Helpers**: `hexToHSL(hex)`, `hslToHex({ h, s, l })`, `readableOn(background)` (ivory or indigo, whichever
has more contrast), `darkVariant(hex)` (same hue, lightness at least 62%).

## Accessibility

- Text on the action colour is picked by WCAG contrast (`readableOn`), so a pale tenant colour gets dark text.
- Still check the tenant colour against your surfaces; a very light brand colour is weak as link text on ivory.

## RTL & i18n

- Colours only; nothing to mirror.

## Styling & tokens

- Writes inline custom properties on the target. The server render uses the manifest; the tenant colours
  apply after hydration. To avoid the flash, also print the same variables in a `<style>` from the server.

## Do / Don't

- Do validate colours when the tenant saves them; the provider only skips invalid ones.
- Do keep your product mark somewhere when a tenant logo replaces it.
- Don't recolour status colours (success, danger); they keep their meaning across tenants.

## Related

- [`ProductMark`](https://docs.nasaqui.com/components/product-mark)
- [`ColorPicker`](https://docs.nasaqui.com/components/color-picker)
- [`Switchers`](https://docs.nasaqui.com/components/switchers)

## Lab

https://docs.nasaqui.com/?path=/docs/components-brand-branding-provider--docs

## Code

### React

```tsx
import { BrandingProvider, NasaqProvider, useBranding } from "@fadymondy/nasaq/web";
import type { ReactNode } from "react";

declare const org: { name: string; brandColor: string | null; logoUrl: string | null };

function OrgLogo() {
  const b = useBranding();
  return b?.logoUrl ? <img src={b.logoUrl} alt={b.name ?? ""} className="h-6" /> : <span>{b?.name}</span>;
}

export function App({ children }: { children: ReactNode }) {
  return (
    <NasaqProvider>
      <BrandingProvider brand={org.brandColor ?? undefined} logoUrl={org.logoUrl} name={org.name}>
        <OrgLogo />
        {children}
      </BrandingProvider>
    </NasaqProvider>
  );
}
```

### shadcn

```tsx
import { BrandingProvider, NasaqProvider, useBranding } from "@/components/ui/branding-provider";
import type { ReactNode } from "react";

declare const org: { name: string; brandColor: string | null; logoUrl: string | null };

function OrgLogo() {
  const b = useBranding();
  return b?.logoUrl ? <img src={b.logoUrl} alt={b.name ?? ""} className="h-6" /> : <span>{b?.name}</span>;
}

export function App({ children }: { children: ReactNode }) {
  return (
    <NasaqProvider>
      <BrandingProvider brand={org.brandColor ?? undefined} logoUrl={org.logoUrl} name={org.name}>
        <OrgLogo />
        {children}
      </BrandingProvider>
    </NasaqProvider>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { defineComponent, h } from "vue";
import { NqBrandingProvider, NqButton, useBranding } from "@fadymondy/nasaq/vue";

const org = { name: "Acme Clinic", brandColor: "#0A7C66" as string | null, logoUrl: null as string | null };

// Reads the nearest provider: the tenant logo, or its name when it has none.
const OrgLogo = defineComponent({
  setup() {
    const b = useBranding();
    return () => (b?.value.logoUrl ? h("img", { src: b.value.logoUrl, alt: b.value.name ?? "", class: "h-6" }) : h("span", b?.value.name));
  },
});
// Scope the colours to this block so a preview does not recolour the whole page.
const scope = () => document.getElementById("branding-demo");
</script>

<template>
  <NqBrandingProvider :brand="org.brandColor ?? undefined" :logo-url="org.logoUrl" :name="org.name" :target="scope">
    <div id="branding-demo" class="flex items-center gap-4">
      <OrgLogo />
      <NqButton variant="primary">Book a visit</NqButton>
    </div>
  </NqBrandingProvider>
</template>
```

### Blade

```blade
<x-nq::branding-provider brand="#0A7C66" name="Acme Clinic" target="#branding-demo">
    <div id="branding-demo" class="flex items-center gap-4">
        <img x-show="logoUrl" x-bind:src="logoUrl" x-bind:alt="name" class="h-6" style="display: none">
        <span x-show="! logoUrl" x-text="name">Acme Clinic</span>
        <x-nq::button variant="primary">Book a visit</x-nq::button>
    </div>
</x-nq::branding-provider>
```

### HTML + Alpine

```html
<div data-slot="branding-provider" x-data="nqBrandingProvider(JSON.parse('{\u0022brand\u0022:\u0022#0A7C66\u0022,\u0022brandDark\u0022:null,\u0022action\u0022:null,\u0022actionDark\u0022:null,\u0022accent\u0022:null,\u0022logoUrl\u0022:null,\u0022name\u0022:\u0022Acme Clinic\u0022}'), '#branding-demo')"
    class="contents">
    <div id="branding-demo" class="flex items-center gap-4">
        <img x-show="logoUrl" x-bind:src="logoUrl" x-bind:alt="name" class="h-6" style="display: none">
        <span x-show="! logoUrl" x-text="name">Acme Clinic</span>
        <button data-slot="button"
     type="button"                         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 px-[var(--nq-control-pad)]">
        Book a visit</button>
    </div>
</div>
```
