> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getpara.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Style the Modal

> Customize the appearance of the Para Modal with colors, fonts, and CSS overrides.

export const Link = ({href, label, newTab = false}) => {
  const [isHovered, setIsHovered] = useState(false);
  return <a href={href} target={newTab ? '_blank' : '_self'} rel={newTab ? 'noopener noreferrer' : undefined} className="not-prose inline-block relative text-black font-semibold cursor-pointer border-b-0 no-underline" onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
      {label}
      <span className={`absolute left-0 bottom-0 w-full rounded-sm bg-gradient-to-r from-orange-600 to-purple-600 transition-all duration-300 ${isHovered ? 'h-0.5' : 'h-px'}`} />
    </a>;
};

export const MethodDocs = ({name, description, parameters = [], returns, deprecated = false, since = null, async = false, static: isStatic = false, tag = null, defaultExpanded = false, preventCollapse = false, id = 'method'}) => {
  const [isExpanded, setIsExpanded] = useState(defaultExpanded || preventCollapse);
  const [isHovered, setIsHovered] = useState(false);
  const [isCopied, setIsCopied] = useState(false);
  const [hoveredParam, setHoveredParam] = useState(null);
  const [hoveredReturn, setHoveredReturn] = useState(false);
  const parseMethodName = fullName => {
    const match = fullName.match(/^([^(]+)(\()([^)]*)(\))$/);
    if (match) {
      return {
        name: match[1],
        openParen: match[2],
        params: match[3],
        closeParen: match[4]
      };
    }
    return {
      name: fullName,
      openParen: '',
      params: '',
      closeParen: ''
    };
  };
  const methodParts = parseMethodName(name);
  const handleCopy = e => {
    e.stopPropagation();
    navigator.clipboard.writeText(name);
    setIsCopied(true);
    setTimeout(() => setIsCopied(false), 2000);
  };
  return <div className={`not-prose rounded-2xl border border-gray-200 overflow-hidden transition-colors duration-200 mb-6 ${isHovered && !preventCollapse ? 'bg-gray-50' : 'bg-white'}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
      <button onClick={() => !preventCollapse && setIsExpanded(!isExpanded)} className={`w-full bg-transparent p-6 border-none text-left ${preventCollapse ? 'cursor-default' : 'cursor-pointer'}`}>
        <div className="flex items-start justify-between gap-4">
          <div className="flex-1 flex flex-col gap-2">
            <div className="flex items-center gap-3 flex-wrap">
              <div className="flex items-center gap-2">
                {async && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-purple-200 text-purple-800 rounded-lg">
                    async
                  </span>}
                {isStatic && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-violet-200 text-violet-900 rounded-lg">
                    static
                  </span>}
                {tag && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-teal-200 text-teal-800 rounded-lg">
                    {tag}
                  </span>}
              </div>

              <code className="text-lg font-mono font-semibold text-gray-900">
                <span>{methodParts.name}</span>
                <span className="text-gray-500 font-normal">{methodParts.openParen}</span>
                <span className="text-blue-600 font-normal">{methodParts.params}</span>
                <span className="text-gray-500 font-normal">{methodParts.closeParen}</span>
              </code>

              {deprecated && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-red-100 text-red-800 rounded-lg flex items-center gap-0.5">
                  ⚠ Deprecated
                </span>}
              {since && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-blue-100 text-blue-800 rounded-lg">
                  Since v{since}
                </span>}
            </div>

            <p className="text-sm text-gray-600 leading-6 m-0">
              {description}
            </p>
          </div>

          <div className="flex items-center gap-2 flex-shrink-0">
            <button onClick={handleCopy} className="p-2 bg-transparent border-none rounded-md cursor-pointer transition-colors duration-200 text-gray-500 hover:bg-gray-100" title="Copy method signature">
              {isCopied ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#16a34a" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <polyline points="20 6 9 17 4 12"></polyline>
                </svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                  <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
                </svg>}
            </button>

            {!preventCollapse && <span className="text-gray-400">
                {isExpanded ? <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                    <polyline points="18 15 12 9 6 15"></polyline>
                  </svg> : <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                    <polyline points="6 9 12 15 18 9"></polyline>
                  </svg>}
              </span>}
          </div>
        </div>
      </button>

      <div className={`overflow-hidden transition-all duration-300 ease-in-out px-6 border-t border-gray-200 ${isExpanded ? 'max-h-[2000px] opacity-100 pb-6' : 'max-h-0 opacity-0 pb-0'}`}>
        {parameters.length > 0 && <div className="pt-6">
            <div className="flex items-center gap-2 mb-3">
              <h3 className="text-sm font-semibold text-gray-700 uppercase tracking-wider m-0">
                Parameters
              </h3>
              <span className="text-xs text-gray-500">({parameters.length})</span>
            </div>
            <div>
              {parameters.map((param, index) => <div key={index} className={`pl-4 border-l-2 transition-colors duration-200 ${hoveredParam === index ? 'border-gray-300' : 'border-gray-200'} ${index < parameters.length - 1 ? 'mb-3' : ''}`} onMouseEnter={() => setHoveredParam(index)} onMouseLeave={() => setHoveredParam(null)}>
                  <div className="flex items-baseline gap-2 mb-1 flex-wrap">
                    <code className="font-mono text-sm font-medium text-gray-900">
                      {param.name}
                    </code>
                    <span className="text-sm text-gray-500">:</span>
                    {param.typeLink ? <a href={param.typeLink} className="no-underline">
                        <code className="font-mono text-sm text-blue-600 bg-transparent px-1 py-0.5 rounded-md cursor-pointer transition-all duration-200 hover:bg-gray-100 hover:text-blue-700">
                          {param.type}
                        </code>
                      </a> : <code className="font-mono text-sm text-blue-600 bg-transparent px-1 py-0.5 rounded-md">
                        {param.type}
                      </code>}
                    {param.required && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-yellow-100 text-yellow-800 rounded-lg">
                        Required
                      </span>}
                    {param.optional && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-gray-100 text-gray-600 rounded-lg">
                        Optional
                      </span>}
                  </div>
                  {param.description && <p className="text-sm text-gray-600 mt-1 mb-0">
                      {param.description}
                    </p>}
                  {param.defaultValue !== undefined && <p className="text-sm text-gray-500 mt-1">
                      Default: <code className="font-mono text-[0.625rem] bg-gray-100 px-1.5 py-0.5 rounded-lg">{param.defaultValue}</code>
                    </p>}
                </div>)}
            </div>
          </div>}

        {returns && <div className={`${parameters.length > 0 ? 'mt-6' : 'pt-6'}`}>
            <h3 className="text-sm font-semibold text-gray-700 uppercase tracking-wider m-0 mb-3">
              Returns
            </h3>
            <div className={`pl-4 border-l-2 transition-colors duration-200 ${hoveredReturn ? 'border-gray-300' : 'border-gray-200'}`} onMouseEnter={() => setHoveredReturn(true)} onMouseLeave={() => setHoveredReturn(false)}>
              <div className="flex items-baseline gap-2 mb-1">
                {returns.typeLink ? <a href={returns.typeLink} className="no-underline">
                    <code className="font-mono text-sm text-blue-600 bg-transparent px-1 py-0.5 rounded cursor-pointer transition-all duration-200 hover:bg-gray-100 hover:text-blue-700">
                      {returns.type}
                    </code>
                  </a> : <code className="font-mono text-sm text-blue-600 bg-transparent px-1 py-0.5 rounded">
                    {returns.type}
                  </code>}
              </div>
              {returns.description && <p className="text-sm text-gray-600 mt-1 mb-0">
                  {returns.description}
                </p>}
            </div>
          </div>}
      </div>
    </div>;
};

Customize the visual appearance of the Para Modal to match your application's design system. Configure branding in the Developer Portal by default, and use `configOverrides` when a modal instance needs code-level overrides.

## Logo Configuration

<MethodDocs
  name="logo"
  description="Specify the logo image to display throughout the modal"
  defaultExpanded={true}
  parameters={[
{
  name: "logo",
  type: "string",
  required: false,
  description: "URL to your logo image"
}
]}
/>

```tsx theme={null}
configOverrides={{
  modalConfig: {
    logo: "https://yourdomain.com/logo.png"
  }
}}
```

<Tip>For optimal display, use a logo image with dimensions of 372px x 160px.</Tip>

## Theme Configuration

<MethodDocs
  name="theme"
  description="Theme configuration for the modal"
  defaultExpanded={true}
  parameters={[
{
  name: "foregroundColor",
  type: "string",
  required: false,
  description: "Primary color for interactive elements. Drives the entire UI palette (buttons, accents, rings) via OKLCH color mixing."
},
{
  name: "backgroundColor",
  type: "string",
  required: false,
  description: "Background color of the modal"
},
{
  name: "accentColor",
  type: "string",
  required: false,
  description: "**@deprecated** Use `foregroundColor` instead. When both are provided, `accentColor` takes precedence and is used as the foreground color."
},
{
  name: "foregroundMixRatio",
  type: "number",
  required: false,
  defaultValue: "0.04",
  description: "Controls how much the foreground color mixes into UI surfaces like buttons, inputs, and borders. Lower values (e.g. 0.04) produce subtler tinting; higher values (e.g. 0.15) make the foreground color more prominent across surfaces."
},
{
  name: "mode",
  type: "'light' | 'dark'",
  required: false,
  description: "Override the auto-detected theme mode. The system automatically determines light/dark from the background color lightness — only set this if the auto-detection misclassifies your background color (e.g., an ambiguous color near the light/dark boundary)."
},
{
  name: "font",
  type: "string",
  required: false,
  description: "Font family to use throughout the modal"
},
{
  name: "borderRadius",
  type: "string",
  required: false,
  description: "Border radius for UI elements: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'"
},
{
  name: "cssOverrides",
  type: "Record<string, string>",
  required: false,
  description: "Raw CSS custom property overrides applied after theme generation. Advanced escape hatch for fine-grained control over individual UI elements."
}
]}
/>

In v3, the theme system uses **OKLCH color mixing** to generate a complete, harmonious palette from just two colors: `foregroundColor` and `backgroundColor`. You no longer need to specify individual colors for buttons, accents, text, or dark mode variants.

### Basic Theme Example

```tsx theme={null}
configOverrides={{
  themeConfig: {
    foregroundColor: "#007AFF",
    backgroundColor: "#FFFFFF",
    mode: "light",
    borderRadius: "md",
    font: "Arial, sans-serif"
  }
}}
```

### Dark Mode Configuration

Just provide dark colors for `backgroundColor` and `foregroundColor`. The system auto-detects dark mode from the background color lightness and generates an appropriate palette:

```tsx theme={null}
configOverrides={{
  themeConfig: {
    foregroundColor: "#0A84FF",
    backgroundColor: "#1C1C1E",
  }
}}
```

<Info>
  In v3, separate dark mode color properties (`darkForegroundColor`, `darkBackgroundColor`, `darkAccentColor`) are no longer needed.
  The palette is auto-generated from your `foregroundColor` and `backgroundColor`, and dark/light mode is auto-detected from the background color lightness.
  Use the `mode` property only if you need to override the auto-detection (e.g., for a background color near the light/dark boundary that gets misclassified).
</Info>

## Foreground Mix Ratio

The `foregroundMixRatio` controls how much your `foregroundColor` bleeds into UI surfaces like buttons, inputs, and borders. The default is `0.04`.

```tsx theme={null}
configOverrides={{
  themeConfig: {
    foregroundColor: "#007AFF",
    backgroundColor: "#FFFFFF",
    foregroundMixRatio: 0.12
  }
}}
```

| Value  | Effect                                                                 |
| ------ | ---------------------------------------------------------------------- |
| `0.04` | Default, subtle tinting: surfaces stay close to the background color   |
| `0.08` | Moderate tinting: foreground color is more visible in surfaces         |
| `0.15` | Strong tinting: foreground color is more prominent across all surfaces |

<Tip>
  If your background has color (is not a neutral gray/white/black), the theme system uses the background color's hue for surface variations.
  If your background is neutral, the foreground color provides the hue for surfaces via the mix ratio.
</Tip>

## Advanced Customization with CSS Overrides

For granular control over specific UI elements, use the `cssOverrides` property to set raw CSS custom properties. Overrides are applied **after** the palette is generated — they replace specific generated values without affecting the rest of the theme — and are **scoped to the Para modal**, so they never leak into your app's styles.

```tsx theme={null}
configOverrides={{
  themeConfig: {
    foregroundColor: "#007AFF",
    backgroundColor: "#FFFFFF",
    mode: "light",
    cssOverrides: {
      "--para-color-primary": "#007AFF",
      "--para-color-accent": "#1A1A2E",
      "--para-color-destructive": "#E74C3C"
    }
  }
}}
```

<Warning>
  `cssOverrides` is an advanced escape hatch. Most applications will get excellent results using just `foregroundColor` and `backgroundColor`.
  Only use `cssOverrides` when you need to override a specific generated color.
</Warning>

### Where to set overrides

| Surface                    | Where                                     | Scope               |
| -------------------------- | ----------------------------------------- | ------------------- |
| `themeConfig.cssOverrides` | In code, on `configOverrides.themeConfig` | This modal instance |

The SDK can read CSS override values that already exist on partner config, but the Developer Portal does not expose a CSS override editor today.

### Available CSS Variables

These are the custom properties the modal actually consumes, so overriding them takes effect:

| Variable                            | Description                                    |
| ----------------------------------- | ---------------------------------------------- |
| `--para-color-background`           | Modal background                               |
| `--para-color-foreground`           | Primary text color                             |
| `--para-color-primary`              | Primary accent (buttons, links, active states) |
| `--para-color-primary-foreground`   | Text on primary-colored elements               |
| `--para-color-secondary`            | Secondary element backgrounds                  |
| `--para-color-secondary-foreground` | Text on secondary elements                     |
| `--para-color-muted`                | Muted / input backgrounds                      |
| `--para-color-muted-foreground`     | Placeholder and subtle text                    |
| `--para-color-accent`               | Accent highlights                              |
| `--para-color-accent-foreground`    | Text on accent elements                        |
| `--para-color-popover`              | Popover background                             |
| `--para-color-popover-foreground`   | Popover text                                   |
| `--para-color-border`               | Border color                                   |
| `--para-color-input`                | Input background                               |
| `--para-color-ring`                 | Focus ring color                               |
| `--para-color-destructive`          | Destructive / error color                      |
| `--para-radius`                     | Global border radius                           |

<Note>
  To change the font, use the `font` property (below) — **not** a CSS override. The font family is applied directly to the modal, not through a `--para-*` variable.
</Note>

## Custom Fonts

You can use custom fonts by importing them in your global CSS and specifying the font family:

```tsx theme={null}
configOverrides={{
  themeConfig: {
    font: "Inter, sans-serif"
  }
}}
```

<Tip>
  Ensure your custom font is loaded before the modal renders for the best user experience.
</Tip>

## Password & PIN Screen Theme Limitations

<Warning>
  Password and PIN authentication screens are rendered in an iframe. The SDK passes the resolved theme when it builds portal URLs, so iframe screens can reflect the same Developer Portal theme or `configOverrides.themeConfig`.

  Dynamic theme changes made after an iframe URL has been generated may not affect that iframe until a new URL is generated.
</Warning>

## Complete Theming Example

```tsx theme={null}
<ParaProvider
  paraClientConfig={{
    apiKey: process.env.REACT_APP_PARA_API_KEY || "",
  }}
  config={{
    appName: "Your App Name"
  }}
  configOverrides={{
    modalConfig: {
      logo: "https://yourdomain.com/logo.png"
    },
    themeConfig: {
      foregroundColor: "#007AFF",
      backgroundColor: "#FFFFFF",
      mode: "light",
      borderRadius: "md",
      font: "Inter, sans-serif",
      foregroundMixRatio: 0.04
    }
  }}
>
  {children}
</ParaProvider>
```

## Migrating from v2.x Theming

If you're upgrading from v2.x, see the [Migration Guide](/v3/introduction/migration-to-v3) for a detailed property mapping and before/after examples.

## Modal Designer

Test your theme configuration with our interactive tool:

<Card horizontal title="Modal Designer" imgUrl="/images/v3/general-customize.png" href="https://demo.getpara.com/" description="Interactive tool to preview and test modal configurations in real-time." />
