# Design System Shield - Blocks Component Protection Guide

## Overview

This guide documents how to protect Blocks components from `@chainlink/design-system` global styles that leak into elements when both libraries are used in the same app.

**The Problem:** Design-system applies global styles to raw HTML elements (`span`, `label`, `input`, `button`, `p`, `h1-h6`, `select`, `textarea`). When Blocks components render these elements without explicit Tailwind classes for ALL properties, design-system styles bleed through.

**The Solution:** Apply targeted inline Tailwind classes with `!important` modifiers when needed to override design-system globals.

---

## Approach: Targeted Inline Fixes

We fix each component individually by:
1. Identifying which elements are affected
2. Adding shield classes directly to those elements
3. Using `!important` prefix (e.g., `!border`) when design-system uses stronger specificity

This is preferred over a global CSS file because:
- Only affected components carry the fix
- No additional CSS bundle overhead
- Fixes are co-located with the component code
- Easier to remove when design-system is deprecated

---

## Design-System Conflicting Styles Reference

### `<button>` Elements - **CRITICAL**

**Design-system sets:**
```css
:where(button) {
  appearance: none;
  cursor: pointer;
  font-family: var(--font-family-text);
  text-decoration: none;
  display: inline-flex;
  font-size: 0.875rem;
  background: transparent;
  border: none;                              /* KILLS ALL BORDERS */
  color: var(--color-text-primary);
}
```

**Shield classes to apply:**
```
!border !border-solid !border-input-border hover:!border-input-border-active
```

**Note:** The `border: none` is the killer. Even though Blocks' Button has `border border-input-border`, design-system's rule wins. Must use `!important`.

---

### `<span>` and `<label>` Elements

**Design-system sets:**
```css
:where(span, label) {
  font-family: var(--font-family-text);      /* Inter, system fonts */
  font-weight: var(--font-weight-normal);    /* 400 */
  color: var(--color-text-primary);          /* gray-600 */
  line-height: 1rem;                         /* 16px - THIS IS THE KILLER */
  font-size: 0.875rem;                       /* 14px */
}
```

**Shield classes to apply:**
```
font-sans text-sm leading-normal text-inherit
```
Or use `typographyVariants({ variant: 'body-s' })` which includes all of these.

**Minimal fix (if parent already sets font/color):**
```
leading-normal
```

---

### `<p>` Elements

**Design-system sets:**
```css
:where(p) {
  font-family: var(--font-family-text);
  font-size: 1rem;
  line-height: 1.5;
  font-weight: var(--font-weight-normal);
  color: var(--color-text-primary);
  max-width: 640px;                          /* THIS BREAKS LAYOUTS */
  margin-bottom: var(--space-6x);            /* 24px */
}
```

**Shield classes to apply:**
```
max-w-none m-0
```
The `typographyVariants` base already includes `m-0` but NOT `max-w-none`.

---

### `<h1>` - `<h6>` Elements

**Design-system sets:**
```css
:where(h1, h2, h3, h4, h5, h6) {
  font-family: var(--font-family-text-heading);
  line-height: 1.3;
  font-weight: var(--font-weight-medium);    /* 500 */
  color: var(--color-text-heading);
  margin-bottom: var(--space-2x to --space-6x);
}
/* Plus font-size per heading level */
```

**Shield classes to apply:**
```
m-0
```
The `typographyVariants` heading variants handle font properties. Just ensure `m-0` is present.

---

### `<input>` and `<textarea>` Elements

**Design-system sets:**
```css
:where(input, textarea) {
  border: var(--border-width-primary) solid var(--color-border-secondary);
  font-family: var(--font-family-text);
  font-size: 0.875rem;
  color: var(--color-text-value);
  outline: none;
  background: var(--white);
}
```

**Blocks' Input/Textarea components already handle these.** No action needed.

**For inputs inside other components (like CommandInput):**
```
border-none bg-transparent font-sans text-sm text-foreground outline-none appearance-none
```

---

### `<select>` Elements

**Design-system sets:**
```css
:where(select) {
  border: var(--border-width-primary) solid var(--color-border-secondary);
  font-family: var(--font-family-text);
  font-size: 0.875rem;
  color: var(--color-text-value);
  outline: none;
  background: var(--white);
  border-radius: var(--border-radius-primary);
  padding: var(--space-2x) var(--space-10x) var(--space-2x) var(--space-4x);
  appearance: none;
  background-image: url(...);                /* DROPDOWN CARET IMAGE */
  cursor: pointer;
}
```

**Shield classes to apply:**
```
appearance-none bg-none border-none p-0 rounded-none font-sans text-sm cursor-default
```

---

### `<input type="checkbox">` and `<input type="radio">`

**Design-system sets:**
```css
:where(input:is([type='checkbox'], [type='radio'])) {
  padding: 0;
  height: 16px;
  width: 16px;
  min-width: 16px;
  border-radius: var(--border-radius-primary);
  appearance: none;
  display: inline-block;
  vertical-align: middle;
  /* Plus checked state with background-image */
}
```

**Blocks' Checkbox uses Radix which renders a button, not input.** Usually safe.

**If using raw checkbox/radio:**
```
appearance-none p-0 m-0 border-0 bg-none
```

---

## SVG Icon Color Issues

**Problem:** Design-system may affect icon colors through inherited `color` properties.

**Fix:** Use `!text-{color}` on SVG icons:
```tsx
<SvgIcon className="!text-input-muted-foreground" />
```

Avoid using `color` prop on icons when in design-system context - use className instead.

---

## Component Shield Procedure

When asked to "shield" a component from design-system:

### Step 1: Find all raw HTML elements

```bash
grep -E "<(span|label|p|select|input|textarea|button)" ComponentName.tsx
```

### Step 2: Check each element's className

For each element found:
1. Does it use `typographyVariants()`? → Usually safe
2. Does it use `inputVariants()`? → Usually safe
3. Is it a raw element with partial classes? → Needs shield
4. Is it inside a Radix/cmdk primitive? → Check if styled
5. Is it a `<button>` with border classes? → Needs `!border` fix

### Step 3: Apply shield classes

| Element | Shield Classes |
|---------|---------------|
| `<button>` with border | `!border !border-solid !border-{color} hover:!border-{hover-color}` |
| `<span>` | `font-sans text-sm leading-normal text-inherit` |
| `<label>` | `font-sans text-sm leading-normal text-inherit` |
| `<p>` | `max-w-none m-0` (add to existing) |
| `<select>` | `appearance-none bg-none` |
| `<input>` in composites | `appearance-none bg-transparent border-none` |
| SVG icons | `!text-{color}` instead of `color` prop |

### Step 4: Check icon/element alignment

Design-system resets can affect flexbox alignment. If icons appear misaligned:
```tsx
// Wrap in flex container with explicit alignment
<div className="flex items-center justify-center h-3 w-3">
  <SvgIcon className="h-2 w-2" />
</div>
```

### Step 5: Verify with cn()

Ensure shield classes are merged properly:
```tsx
// Good - shield classes first, can be overridden by component classes
<span className={cn('font-sans text-sm leading-normal', 'text-muted-foreground', otherClasses)}>

// For !important classes, order doesn't matter
<button className={cn('!border !border-solid !border-input-border', otherClasses)}>
```

### Step 6: Run linter

```bash
pnpm nx lint blocks --files=libs/blocks/src/components/ComponentName/ComponentName.tsx
```

Fix any class ordering issues (Tailwind's class-sort plugin).

---

## Quick Reference: Shield Class Sets

### Button Elements (with border)
```typescript
const buttonBorderShield = '!border !border-solid !border-input-border hover:!border-input-border-active'
```

### Text Elements (span, label)
```typescript
const textShield = 'font-sans text-sm leading-normal text-inherit'
```

### Paragraph Elements (p)
```typescript
const paragraphShield = 'max-w-none m-0'
```

### Select Elements
```typescript
const selectShield = 'appearance-none bg-none border-none rounded-none p-0'
```

### Input Elements (inside composites)
```typescript
const inputShield = 'appearance-none bg-transparent border-none outline-none'
```

### Icon Color Override
```typescript
const iconColorShield = '!text-input-muted-foreground' // or other color
```

---

## Already Protected Components

These components use variants that include shield properties:

- `Button` - uses `buttonVariants` with full styling
- `Input` - uses `inputVariants` with full styling  
- `Textarea` - uses `textareaVariants` with full styling
- `Select` - uses `inputVariants` on trigger
- `Typography` - uses `typographyVariants` (needs `max-w-none` added to base)
- `Label` - uses `typographyVariants`

---

## Components Shielded (Changelog)

### ✅ MultiSelect - DONE
**Issues fixed:**
1. Trigger button border not showing → Added `!border !border-solid !border-input-border`
2. Trigger border hover state not working → Added `hover:!border-input-border-active`
3. Caret icon wrong color when closed → Changed from `color` prop to `!text-input-muted-foreground` class
4. X icon in badges not vertically centered → Changed wrapper to `flex items-center justify-center h-3 w-3`
5. Placeholder text bolder than Select → Added `font-sans text-sm font-normal leading-5` to match Select's `body-s` typography

**Note on #5:** This was a missing style issue, NOT a design-system conflict. Without design-system, the span inherited different font-weight from parent. Design-system accidentally "fixed" it by applying `font-weight: 400` globally. The proper fix is explicit typography classes.

**Files modified:**
- `libs/blocks/src/components/MultiSelect/MultiSelect.tsx`

---

## Components Needing Shield (Known)

- `Command` - CommandInput, CommandShortcut spans
- `RadioGroup` - raw label element
- `Combobox` - similar to MultiSelect
- Components with raw `<p>` elements

---

## Maintenance Notes

1. **New components:** Always use `typographyVariants()` for text, `inputVariants()` for inputs
2. **Raw spans:** If you must use raw `<span>`, apply text shield classes
3. **Typography base:** Consider adding `max-w-none` to `typographyVariants` base
4. **Testing:** Test components in apps that have design-system globals imported (e.g., demo-ui)
5. **Button borders:** Always use `!border` prefix when a button needs visible borders
6. **Icon colors:** Prefer `!text-{color}` className over `color` prop

---

_Last updated: 2026-02-04_
_Maintained by: AI Assistant for papi_
