![snyk-mktg-brand-ui](https://res.cloudinary.com/snyk/image/upload/v1686848505/google-scripts/snyk-brandui-logo.png)

# Snyk BrandUI

[![Total Downloads](https://img.shields.io/npm/dt/@snyk-mktg/brand-ui.svg)](https://www.npmjs.com/package/@snyk-mktg/brand-ui)
[![Latest Release](https://img.shields.io/npm/v/@snyk-mktg/brand-ui.svg)](https://github.com/@snyk-mktg/brand-ui/@snyk-mktg/brand-ui/releases)
[![License](https://img.shields.io/npm/l/@snyk-mktg/brand-ui.svg)](https://github.com/snyk-marketing/@snyk-mktg/brand-ui/blob/main/LICENSE)

---

Welcome to BrandUI, the official styles library for Snyk's Marketing design system.

Full documentation: [https://brand-ui.snyk.marketing/](https://brand-ui.snyk.marketing/)

---

## Usage

### Installation

```bash
npm i @snyk-mktg/brand-ui
# or
pnpm add @snyk-mktg/brand-ui
```

### Pre-compiled CSS

Drop a single stylesheet into your app. No Sass compiler needed.

```html
<link rel="stylesheet" href="node_modules/@snyk-mktg/brand-ui/dist/css/bundle.css">
```

Or import from a bundler:

```js
import '@snyk-mktg/brand-ui/dist/css/bundle.css'
```

### SCSS Source

Requires a Sass compiler. Import the full bundle or individual layers:

```scss
// Full bundle (base tokens + BrandUI theme + components + utilities)
@use '@snyk-mktg/brand-ui/dist/scss/bundle' as *;

// Or pick what you need
@use '@snyk-mktg/brand-ui/dist/scss/base' as *;
@use '@snyk-mktg/brand-ui/dist/scss/components' as *;
@use '@snyk-mktg/brand-ui/dist/scss/utilities' as *;
```

### JS / TypeScript Utilities

BrandUI ships helper functions and typed design-token arrays. Both ESM and CommonJS are supported, and full `.d.ts` declarations are included.

```ts
import { buttonVariants, classNames, colors, getInitials, gridSpacing, icons } from '@snyk-mktg/brand-ui'
```

#### Helpers

| Export                               | Description                                                              |
| ------------------------------------ | ------------------------------------------------------------------------ |
| `classNames`                         | Conditionally join class names (similar to the `classnames` npm package) |
| `getInitials`                        | Extract initials from a full name                                        |
| `caseFormats`                        | Convert between camelCase, kebab-case, and snake_case                    |
| `gridSpacing`, `gridSplit`           | Calculate grid gap and column splits                                     |
| `range`                              | Generate a numeric range array                                           |
| `uid`                                | Generate a unique identifier string                                      |
| `scrollHorizontal`, `scrollVertical` | Programmatic scroll helpers                                              |
| `incrementDisplayValue`              | Animate a counter from 0 to a target value                               |

#### Utilities / Types

| Export                                                           | Description                              |
| ---------------------------------------------------------------- | ---------------------------------------- |
| `buttonVariants`                                                 | Array of all button variant names        |
| `colors`                                                         | Array of all color token names           |
| `icons`                                                          | Array of all icon names                  |
| `logos`                                                          | Array of all logo names                  |
| `layoutSizes`, `branduiPadding`, `branduiSpacing`, `avatarSizes` | Size and spacing scale arrays            |
| `downloadableFiles`                                              | Array of downloadable file type names    |
| `mediaCategories`                                                | Array of media category names            |
| `patchPoses`                                                     | Array of Snyk patch character pose names |

All exports include TypeScript type definitions. The package ships both ESM (`dist/js/index.js`) and CommonJS (`dist/js/cjs/index.js`) builds.

### Available Bundles

| CSS file          | SCSS entry point   | Contents                                                |
| ----------------- | ------------------ | ------------------------------------------------------- |
| `bundle.css`      | `bundle.scss`      | Base tokens + BrandUI theme + components + utilities    |
| `evo-bundle.css`  | `evo-bundle.scss`  | Base tokens + Evo theme + components + utilities        |
| `labs-bundle.css` | `labs-bundle.scss` | Base tokens + Labs theme + components + utilities       |
| `base.css`        | `base.scss`        | Fonts, variables, mixins, resets, typography, layout    |
| `components.css`  | `components.scss`  | All UI components (atoms, molecules, organisms)         |
| `utilities.css`   | `utilities.scss`   | Utility classes (flex, grid, spacing, visibility, etc.) |

### Theming

BrandUI uses a token-driven, two-tier color system. Tier-1 tokens define raw palette values (`--color-pink`, `--color-midnight`). Tier-2 tokens are semantic labels (`--action`, `--success`, `--fail`) that each theme maps to different tier-1 values. Components always reference tier-2 labels, so swapping a theme recolors the entire UI without touching component code.

You can use one of the three built-in themes (BrandUI, Evo, Labs) or create your own.

### Using a Prebuilt Theme

#### Import a bundle directly

The simplest approach — just import one of the built-in bundles. Each bundle includes base tokens, a theme, components, and utilities in a single file.

```scss
// BrandUI theme (default)
@use '@snyk-mktg/brand-ui/dist/scss/bundle' as *;

// Evo theme
@use '@snyk-mktg/brand-ui/dist/scss/evo-bundle' as *;

// Labs theme
@use '@snyk-mktg/brand-ui/dist/scss/labs-bundle' as *;
```

Or with pre-compiled CSS:

```js
import '@snyk-mktg/brand-ui/dist/css/evo-bundle.css'
```

#### Generate token files into your project

If you need the token SCSS files in your own directory (e.g. to compose them with your own Sass pipeline), you can generate them:

```js
const { buildAllTokens } = require('@snyk-mktg/brand-ui/build/build-tokens.cjs')

buildAllTokens({
  outputDir: './my-app/scss',
  themes: ['evo'],
})
```

Then import in SCSS:

```scss
@use 'my-app/scss/base/maps/tokens' as base-tokens;
@use 'my-app/scss/evo/base/maps/tokens' as theme-tokens;
```

### Creating a Custom Theme

Define your own theme with a single JSON file. The token data can include `color-labels`, `font-family`, and/or `text-elements` — all sections are optional, but at least one of `color-labels` or `text-elements` must be present.

**Color Labels** — Map semantic names to base color keys from `tokens/base/colors.json`. Values become `var(--color-{value})` references.

**Font Families** — Define named font stacks that text elements reference via `{font-family.key}` syntax.

**Text Elements** — Define typography presets. Each text element generates CSS custom properties for size, line-height, weight, family, and letter-spacing. Optional `text-transform` and `responsive.mobile.target` fields are also supported.

#### Minimal Example (color labels only)

```json
{
  "color-labels": {
    "default": { "$value": "white" },
    "action": { "$value": "coral" },
    "action-dark": { "$value": "coral" },
    "success": { "$value": "mint" },
    "fail": { "$value": "rose" }
  }
}
```

#### Full Example (color labels + typography)

```json
{
  "color-labels": {
    "default": { "$value": "white" },
    "action": { "$value": "coral" },
    "action-dark": { "$value": "coral" }
  },
  "font-family": {
    "my-sans": { "$value": "Inter, sans-serif", "$type": "fontFamily" }
  },
  "text-elements": {
    "hero-title-large": {
      "weight": { "$value": 700 },
      "font-size": { "$value": "4rem" },
      "line-height": { "$value": "4.5rem" },
      "family": { "$value": "{font-family.my-sans}" },
      "letter-spacing": { "$value": "-0.01em" },
      "responsive": {
        "mobile": { "target": "{text-elements.hero-title-small}" }
      }
    },
    "hero-title-small": {
      "weight": { "$value": 700 },
      "font-size": { "$value": "2.5rem" },
      "line-height": { "$value": "3rem" },
      "family": { "$value": "{font-family.my-sans}" },
      "letter-spacing": { "$value": "-0.01em" }
    }
  }
}
```

#### Build the Theme

```js
const { buildCustomTheme } = require('@snyk-mktg/brand-ui/build/build-tokens.cjs')

buildCustomTheme({
  tokenFile: './tokens/my-theme.json',
  outputDir: './scss',
  themeName: 'my-theme',
})
```

This generates `./scss/my-theme/base/maps/_tokens.scss`.

You can also pass pre-parsed data instead of a file path:

```js
buildCustomTheme({
  tokenData: { 'color-labels': { action: { $value: 'coral' } } },
  outputDir: './scss',
  themeName: 'my-theme',
})
```

#### Import in SCSS

```scss
// Base tokens from the npm package
@use '@snyk-mktg/brand-ui/dist/scss/base/maps/tokens' as base-tokens;

// Your custom theme
@use './scss/my-theme/base/maps/tokens' as theme-tokens;

// Components and utilities from the npm package
@use '@snyk-mktg/brand-ui/dist/scss/base' as *;
@use '@snyk-mktg/brand-ui/dist/scss/components' as *;
@use '@snyk-mktg/brand-ui/dist/scss/utilities' as *;
```

#### Text Element Fields Reference

| Field                      | Required | Example                                 | Notes                                |
| -------------------------- | -------- | --------------------------------------- | ------------------------------------ |
| `weight`                   | Yes      | `{ "$value": 700 }`                     | CSS font-weight                      |
| `font-size`                | Yes      | `{ "$value": "2rem" }`                  | Any CSS size value                   |
| `line-height`              | Yes      | `{ "$value": "2.5rem" }`                | Any CSS size value                   |
| `family`                   | Yes      | `{ "$value": "{font-family.my-sans}" }` | Must reference a `font-family` entry |
| `letter-spacing`           | Yes      | `{ "$value": "-0.01em" }`               | CSS letter-spacing                   |
| `text-transform`           | No       | `{ "$value": "uppercase" }`             | CSS text-transform                   |
| `responsive.mobile.target` | No       | `"{text-elements.hero-title-small}"`    | Scales down on screens below 768px   |

---

## Development

### Prerequisites

- Node.js (LTS recommended)
- pnpm 11

### Setup

```bash
pnpm install
pnpm build:tokens
pnpm build
```

### Scripts

| Script              | Purpose                                                         |
| ------------------- | --------------------------------------------------------------- |
| `pnpm build`        | Full production build (SCSS to CSS + TypeScript to JS via Gulp) |
| `pnpm build:tokens` | Regenerate `_tokens.scss` files from JSON token sources         |
| `pnpm dev:css`      | Watch SCSS files and recompile on change                        |
| `pnpm compile`      | TypeScript-only build (emits ESM + CJS to `dist/js/`)           |
| `pnpm test`         | Run the Vitest test suite                                       |
| `pnpm lint`         | Run Biome, Stylelint, and Prettier checks                       |
| `pnpm lint:fix`     | Auto-fix Biome, Stylelint, and Prettier issues                  |
| `pnpm fmt`          | Format the repo with Biome and generated SCSS with Prettier     |
| `pnpm fmt:scss`     | Format generated SCSS with Prettier                             |
| `pnpm typecheck`    | Run TypeScript without emitting files                           |

### Local Development

**Option 1 — Live Server (isolated SCSS development)**

1. Install the [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) VS Code extension.
2. Create an `index.html` at the project root that links to `dist/css/dev/index.css`.
3. Run `pnpm dev:css` to watch SCSS and recompile on save.
4. Open the HTML file with Live Server to preview changes in real time.

**Option 2 — pnpm link (integration testing)**

1. Run `pnpm link --global` in this repo to register the package locally.
2. In your consuming project, run `pnpm link --global @snyk-mktg/brand-ui`.
3. Changes in this repo are immediately reflected in the consuming project.
4. When finished, run `pnpm unlink @snyk-mktg/brand-ui` in the consuming project and `pnpm install --force` to restore the published version.

### Architecture

BrandUI uses a **token-driven, two-tier design**. Design decisions are defined as JSON tokens and compiled into CSS custom properties that every component consumes. Components never reference raw color values directly; they use semantic labels like `var(--action)` which each theme maps to a different palette color.

#### Directory Layout

```
tokens/                          Source-of-truth JSON (DTCG format)
  base/                          Shared across all themes
    colors.json                  Raw color palette (tier 1)
    spacing.json                 Padding, spacing, content-max
    effects.json                 Shadows, radii, transitions
    breakpoints.json             Device breakpoints
  themes/
    brandui/                     BrandUI theme definitions
      color-labels.json          Semantic color mappings (tier 2)
      typography.json            Font families + text elements
    evo/                         Evo theme definitions
    labs/                        Labs theme definitions

build/                           Token build pipeline (Node.js)
  build-tokens.cjs               Orchestrator
  token-utils.cjs                Shared helpers
  builders/                      One module per token category

dist/
  css/                           Pre-compiled CSS bundles
  scss/                          SCSS source (tokens + base + components + utilities)
    base/maps/_tokens.scss       Generated base CSS vars + Sass lists
    {theme}/base/maps/_tokens.scss  Generated theme vars
  js/                            JS/TS utilities (ESM + CJS)
```

#### Token Flow

```
tokens/*.json  -->  pnpm build:tokens  -->  dist/scss/**/maps/_tokens.scss  -->  components use var(--*)
```

### SCSS Organization

Styles are organized in three layers, with themes applied as a token swap on top:

- **Base** — Fonts, resets, variables, mixins, layout primitives, typography utilities
- **Components** — Atomic design hierarchy: atoms (button, input, badge) → molecules (accordion, card, modal) → organisms (hero, navigation, footer)
- **Utilities** — Single-purpose classes: flex, grid, spacing, visibility, animations, effects, etc.

Each theme (BrandUI, Evo, Labs) loads different `_tokens.scss` files that reassign CSS custom properties. Evo and Labs also add component-level overrides for elements that need visual changes beyond token swaps (e.g. border-radius, animations).

### Token Build System

Running `pnpm build:tokens` produces four files:

| File                                       | Contents                                                             |
| ------------------------------------------ | -------------------------------------------------------------------- |
| `dist/scss/base/maps/_tokens.scss`         | All base CSS vars (`:root`) + Sass name lists + deprecated Sass maps |
| `dist/scss/brandui/base/maps/_tokens.scss` | BrandUI color labels + typography                                    |
| `dist/scss/evo/base/maps/_tokens.scss`     | Evo color labels + typography                                        |
| `dist/scss/labs/base/maps/_tokens.scss`    | Labs color labels + typography                                       |

The base file contains tier-1 values (`--color-pink`, `--padding-small`, `--shadow-medium`, etc.) plus Sass name lists (`$color-names`, `$padding-names`) used for utility class generation.

Each theme file contains tier-2 semantic mappings (`--action: var(--color-pink)`, `--txt-hero-title-large-size: 4rem`, etc.) that components reference.

#### Source Architecture

```
build/
  build-tokens.cjs             Orchestrator — buildAllTokens() + buildCustomTheme()
  token-utils.cjs              Shared helpers (extractTokens, toCssVarLines, readTokenFile, etc.)
  builders/
    sizing.cjs                 Padding, spacing, content-max
    effects.cjs                Shadows, radii, transitions
    breakpoints.cjs            Device breakpoints
    colors.cjs                 Raw color palette
    color-labels.cjs           Per-theme semantic color mappings
    typography.cjs             Per-theme typography + responsive overrides
```
