> ## Documentation Index
> Fetch the complete documentation index at: https://social-b97141fb-auto-generate-llmstxt.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Component Styling

> Level 2: Moderate customization with CSS overrides and advanced theming

For teams who need **deeper styling control** while staying within the UIKit framework. Component Styling allows you to override CSS, customize individual components, and implement advanced theming.

<CardGroup cols={2}>
  <Card title="Styling Control" icon="code">
    Override styles with custom config json
  </Card>

  <Card title="Component Targeting" icon="crosshairs">
    Customize specific components and pages
  </Card>

  <Card title="Advanced Theming" icon="palette">
    Complex color schemes and typography
  </Card>

  <Card title="UIKit Framework" icon="layer-group">
    Still within the UIKit ecosystem
  </Card>
</CardGroup>

## When to Choose Component Styling

Component Styling is ideal when you need:

✅ **Custom brand experiences** - Basic colors and fonts\
✅ **Granular control** - Target specific components or pages\
✅ **Advanced theming** - Complex color schemes and typography\
✅ **Framework benefits** - Want to stay within UIKit's ecosystem

❌ **Simple branding** - `Dynamic UI` is easier for basic needs\
❌ **Complex layouts** - `Fork & Extend` offers more freedom\
❌ **Custom components** - Need source code access for new components

***

## Configuration Model

<Info>
  Component Styling uses a single `config.json` surface for cross-platform overrides. It layers on top of default UIKit tokens (and Dynamic UI if present) using a path pattern: <code>page\_id/component\_id/element\_id</code> with wildcards (`*`).
</Info>

### Path Pattern & Levels

| Level     | Example                      | Description                         | Wildcard Use                           |
| --------- | ---------------------------- | ----------------------------------- | -------------------------------------- |
| Page      | `camera_page/*/*`            | Entire screen / route               | Replace component & element with `*/*` |
| Component | `*/comment_tray_component/*` | Functional unit within a page       | Wildcard page to apply globally        |
| Element   | `story_page/*/close_button`  | Single interactive / visual element | Wildcard component for all instances   |

<Tip>
  Specific beats generic: Element overrides > Component overrides > Page overrides > Global theme.
</Tip>

### Supported Theme Tokens

Every key below may be set under `theme.light` and `theme.dark`. Any key you omit falls back to the
default shown. Defaults are identical across platforms — they ship in the same config template.

<Note>
  **Where each token applies.** Thirteen tokens resolve across **every** UIKit feature:
  `primary_color`, `secondary_color`, `base_color`, `base_shade1_color` through `base_shade4_color`,
  `base_inverse_color`, `alert_color`, `background_color`, `background_shade1_color`,
  `highlight_color`, and `toast_background_color`.

  The rest belong to a revised color set that currently drives **chat** components only. Setting them
  has no effect on other features yet — those continue to resolve through the thirteen above, and
  adopt the expanded set in a later release.
</Note>

#### Core

| Token                     | Purpose                                        | Default (light) | Default (dark) |
| ------------------------- | ---------------------------------------------- | --------------- | -------------- |
| primary\_color            | Actions / highlights                           | `#1054DE`       | `#1054DE`      |
| secondary\_color          | Secondary actions / borders                    | `#292B32`       | `#EBECEF`      |
| base\_color               | Primary text color                             | `#292B32`       | `#EBECEF`      |
| base\_shade1\_color       | Secondary text — subtitles, meta               | `#636878`       | `#A5A9B5`      |
| base\_shade2\_color       | Tertiary text / hints — placeholders, inactive | `#898E9E`       | `#6E7487`      |
| base\_shade3\_color       | Disabled text                                  | `#A5A9B5`       | `#40434E`      |
| base\_shade4\_color       | Faint text / separators                        | `#EBECEF`       | `#292B32`      |
| base\_inverse\_color      | Text on inverted surfaces                      | `#000000`       | `#FFFFFF`      |
| alert\_color              | Errors / destructive                           | `#FA4D30`       | `#FA4D30`      |
| background\_color         | Page and component surface                     | `#FFFFFF`       | `#191919`      |
| background\_shade1\_color | Secondary surface — grouped rows, wells        | `#F6F7F8`       | `#40434E`      |

#### Brand shades

Variants of `primary_color` and `secondary_color` used for hover, pressed, disabled and emphasis states.

| Token                    | Default (light) | Default (dark) |
| ------------------------ | --------------- | -------------- |
| primary\_shade1\_color   | `#4A82F2`       | `#4A82F2`      |
| primary\_shade2\_color   | `#A9C4F9`       | `#A9C4F9`      |
| primary\_shade3\_color   | `#D9E5FC`       | `#D9E5FC`      |
| primary\_shade4\_color   | `#1A4499`       | `#1A4499`      |
| secondary\_shade1\_color | `#636878`       | `#A5A9B5`      |
| secondary\_shade2\_color | `#898E9E`       | `#898E9E`      |
| secondary\_shade3\_color | `#A5A9B5`       | `#40434E`      |
| secondary\_shade4\_color | `#EBECEF`       | `#292B32`      |

#### Neutral greys

A light-to-dark ramp used for borders, dividers, skeletons and muted text. These are mode-independent
by default — set them per mode if your dark theme needs a different ramp.

| Token                        | Default   |   | Token                        | Default   |
| ---------------------------- | --------- | - | ---------------------------- | --------- |
| neutral\_grey\_shade1\_color | `#EBECEF` |   | neutral\_grey\_shade4\_color | `#636878` |
| neutral\_grey\_shade2\_color | `#A5A9B5` |   | neutral\_grey\_shade5\_color | `#40434E` |
| neutral\_grey\_shade3\_color | `#898E9E` |   | neutral\_grey\_shade6\_color | `#292B32` |

#### Destructive

Escalating scale for destructive actions and error states.

| Token                      | Default   |   | Token                      | Default   |
| -------------------------- | --------- | - | -------------------------- | --------- |
| destructive\_shade1\_color | `#F38F96` |   | destructive\_shade4\_color | `#E50B1B` |
| destructive\_shade2\_color | `#EE5C66` |   | destructive\_shade5\_color | `#A30813` |
| destructive\_shade3\_color | `#EA3C49` |   | alert\_shade1\_color       | `#FB7159` |

#### Overlays

Semi-transparent fills for scrims, media overlays and pressed states. Values carry an alpha channel
(`#RRGGBBAA`).

| Token                             | Default     |   | Token                             | Default     |
| --------------------------------- | ----------- | - | --------------------------------- | ----------- |
| transparent\_black\_shade1\_color | `#0000004D` |   | transparent\_white\_shade1\_color | `#FFFFFF1A` |
| transparent\_black\_shade2\_color | `#0000004D` |   | transparent\_white\_shade2\_color | `#FFFFFF1A` |
| transparent\_black\_shade3\_color | `#00000080` |   | transparent\_white\_shade3\_color | `#FFFFFF33` |
| transparent\_black\_shade4\_color | `#00000099` |   | transparent\_white\_shade4\_color | `#FFFFFF4D` |
| transparent\_black\_shade5\_color | `#000000CC` |   | transparent\_white\_shade5\_color | `#FFFFFF4D` |
| transparent\_red\_shade1\_color   | `#FF305A4D` |   | transparent\_white\_shade6\_color | `#FFFFFF4D` |
|                                   |             |   | transparent\_white\_shade7\_color | `#FFFFFF4D` |

#### Fixed and feature colors

| Token                    | Purpose                                              | Default (light) | Default (dark) |
| ------------------------ | ---------------------------------------------------- | --------------- | -------------- |
| white\_color             | Fixed white — surfaces that stay light in both modes | `#FFFFFF`       | `#FFFFFF`      |
| black\_color             | Fixed black — surfaces that stay dark in both modes  | `#000000`       | `#000000`      |
| highlight\_color         | Search and selection highlight                       | `#1054DE`       | `#4A82F2`      |
| live\_color              | Live / streaming indicator                           | `#FF305A`       | `#FF305A`      |
| toast\_background\_color | Toast surface                                        | `#292B32`       | `#40434E`      |
| event\_host\_bg\_color   | Event host badge background                          | `#EAE2FF`       | `#EAE2FF`      |
| event\_host\_fg\_color   | Event host badge text                                | `#4B1BD0`       | `#4B1BD0`      |

### Minimal Config Skeleton

<CodeGroup>
  ```json Full theme theme={null}
  {
    "theme": {
      "light": {
        "primary_color": "#1054DE",
        "secondary_color": "#292B32",
        "base_color": "#292B32",
        "base_shade1_color": "#636878",
        "base_shade2_color": "#898E9E",
        "base_shade3_color": "#A5A9B5",
        "base_shade4_color": "#EBECEF",
        "base_inverse_color": "#000000",
        "alert_color": "#FA4D30",
        "background_color": "#FFFFFF",
        "background_shade1_color": "#F6F7F8",
        "primary_shade1_color": "#4A82F2",
        "primary_shade2_color": "#A9C4F9",
        "primary_shade3_color": "#D9E5FC",
        "primary_shade4_color": "#1A4499",
        "secondary_shade1_color": "#636878",
        "secondary_shade2_color": "#898E9E",
        "secondary_shade3_color": "#A5A9B5",
        "secondary_shade4_color": "#EBECEF",
        "neutral_grey_shade1_color": "#EBECEF",
        "neutral_grey_shade2_color": "#A5A9B5",
        "neutral_grey_shade3_color": "#898E9E",
        "neutral_grey_shade4_color": "#636878",
        "neutral_grey_shade5_color": "#40434E",
        "neutral_grey_shade6_color": "#292B32",
        "destructive_shade1_color": "#F38F96",
        "destructive_shade2_color": "#EE5C66",
        "destructive_shade3_color": "#EA3C49",
        "destructive_shade4_color": "#E50B1B",
        "destructive_shade5_color": "#A30813",
        "alert_shade1_color": "#FB7159",
        "transparent_black_shade1_color": "#0000004D",
        "transparent_black_shade2_color": "#0000004D",
        "transparent_black_shade3_color": "#00000080",
        "transparent_black_shade4_color": "#00000099",
        "transparent_black_shade5_color": "#000000CC",
        "transparent_red_shade1_color": "#FF305A4D",
        "transparent_white_shade1_color": "#FFFFFF1A",
        "transparent_white_shade2_color": "#FFFFFF1A",
        "transparent_white_shade3_color": "#FFFFFF33",
        "transparent_white_shade4_color": "#FFFFFF4D",
        "transparent_white_shade5_color": "#FFFFFF4D",
        "transparent_white_shade6_color": "#FFFFFF4D",
        "transparent_white_shade7_color": "#FFFFFF4D",
        "white_color": "#FFFFFF",
        "black_color": "#000000",
        "highlight_color": "#1054DE",
        "live_color": "#FF305A",
        "toast_background_color": "#292B32",
        "event_host_bg_color": "#EAE2FF",
        "event_host_fg_color": "#4B1BD0"
      },
      "dark": {
        "primary_color": "#1054DE",
        "secondary_color": "#EBECEF",
        "base_color": "#EBECEF",
        "base_shade1_color": "#A5A9B5",
        "base_shade2_color": "#6E7487",
        "base_shade3_color": "#40434E",
        "base_shade4_color": "#292B32",
        "base_inverse_color": "#FFFFFF",
        "alert_color": "#FA4D30",
        "background_color": "#191919",
        "background_shade1_color": "#40434E",
        "primary_shade1_color": "#4A82F2",
        "primary_shade2_color": "#A9C4F9",
        "primary_shade3_color": "#D9E5FC",
        "primary_shade4_color": "#1A4499",
        "secondary_shade1_color": "#A5A9B5",
        "secondary_shade2_color": "#898E9E",
        "secondary_shade3_color": "#40434E",
        "secondary_shade4_color": "#292B32",
        "neutral_grey_shade1_color": "#EBECEF",
        "neutral_grey_shade2_color": "#A5A9B5",
        "neutral_grey_shade3_color": "#898E9E",
        "neutral_grey_shade4_color": "#636878",
        "neutral_grey_shade5_color": "#40434E",
        "neutral_grey_shade6_color": "#292B32",
        "destructive_shade1_color": "#F38F96",
        "destructive_shade2_color": "#EE5C66",
        "destructive_shade3_color": "#EA3C49",
        "destructive_shade4_color": "#E50B1B",
        "destructive_shade5_color": "#A30813",
        "alert_shade1_color": "#FB7159",
        "transparent_black_shade1_color": "#0000004D",
        "transparent_black_shade2_color": "#0000004D",
        "transparent_black_shade3_color": "#00000080",
        "transparent_black_shade4_color": "#00000099",
        "transparent_black_shade5_color": "#000000CC",
        "transparent_red_shade1_color": "#FF305A4D",
        "transparent_white_shade1_color": "#FFFFFF1A",
        "transparent_white_shade2_color": "#FFFFFF1A",
        "transparent_white_shade3_color": "#FFFFFF33",
        "transparent_white_shade4_color": "#FFFFFF4D",
        "transparent_white_shade5_color": "#FFFFFF4D",
        "transparent_white_shade6_color": "#FFFFFF4D",
        "transparent_white_shade7_color": "#FFFFFF4D",
        "white_color": "#FFFFFF",
        "black_color": "#000000",
        "highlight_color": "#4A82F2",
        "live_color": "#FF305A",
        "toast_background_color": "#40434E",
        "event_host_bg_color": "#EAE2FF",
        "event_host_fg_color": "#4B1BD0"
      }
    }
  }
  ```

  ```json Minimal theme={null}
  {
    "preferred_theme": "default",
    "theme": {
      "light": { "primary_color": "#1054DE", "background_color": "#FFFFFF" },
      "dark": { "primary_color": "#1054DE", "background_color": "#191919" }
    },
    "excludes": [],
    "customizations": {}
  }
  ```

  ```json Page Override theme={null}
  {
    "customizations": {
      "camera_page/*/*": {
        "theme": {
          "light": { "primary_color": "#1742C0", "background_color": "#FFFFFF" },
          "dark": { "primary_color": "#508BFF", "background_color": "#101014" }
        }
      }
    }
  }
  ```

  ```json Component Override theme={null}
  {
    "customizations": {
      "*/comment_tray_component/*": {
        "theme": {
          "light": { "primary_color": "#0D62F2" },
          "dark": { "primary_color": "#4C91FF" }
        }
      }
    }
  }
  ```

  ```json Element Override theme={null}
  {
    "customizations": {
      "story_page/*/close_button": {
        "close_icon": "close.png",
        "background_color": "#1A1A1A80"
      }
    }
  }
  ```

  ```json Exclude Example theme={null}
  {
    "excludes": [
      "create_story_page/*/aspect_ratio_button"
    ]
  }
  ```

  ```json Full config.json Example theme={null}
  {
    "preferred_theme": "default",
    "theme": {
      "light": {
        "primary_color": "#1054DE",
        "secondary_color": "#292B32",
        "base_color": "#292b32",
        "base_shade1_color": "#636878",
        "base_shade2_color": "#898e9e",
        "base_shade3_color": "#a5a9b5",
        "base_shade4_color": "#ebecef",
        "alert_color": "#FA4D30",
        "background_color": "#FFFFFF",
      },
      "dark": {
        "primary_color": "#1054DE",
        "secondary_color": "#292B32",
        "base_color": "#ebecef",
        "base_shade1_color": "#a5a9b5",
        "base_shade2_color": "#6e7487",
        "base_shade3_color": "#40434e",
        "base_shade4_color": "#292b32",
        "alert_color": "#FA4D30",
        "background_color": "#191919",
      }
    },
    "excludes": [
    ],
    "customizations": {
      "select_target_page/*/*": {
        "page_theme": {
          "light_theme": {
            "primary_color": "#1D1234",
            "secondary_color": "#AB1234"
          }
        },
        "title": "Share to"
      },
      "select_target_page/*/back_button": {
        "back_icon": "back.png"
      },
      "camera_page/*/*": {
        "resolution": "720p",
        "theme": {
          "light": {
            "primary_color": "#1054DE",
            "secondary_color": "#292B32",
            "base_color": "#292b32",
            "base_shade1_color": "#636878",
            "base_shade2_color": "#898e9e",
            "base_shade3_color": "#a5a9b5",
            "base_shade4_color": "#ebecef",
            "alert_color": "#FA4D30",
            "background_color": "#FFFFFF",
          },
          "dark": {
            "primary_color": "#1054DE",
            "secondary_color": "#292B32",
            "base_color": "#ebecef",
            "base_shade1_color": "#a5a9b5",
            "base_shade2_color": "#6e7487",
            "base_shade3_color": "#40434e",
            "base_shade4_color": "#292b32",
            "alert_color": "#FA4D30",
            "background_color": "#191919",
          }
        }
      },
      "camera_page/*/close_button": {
        "close_icon": "close.png",
        "background_color": "#80000000"
      },
      "create_story_page/*/*": {},
      "create_story_page/*/back_button": {
        "back_icon": "back.png",
        "background_color": "#80000000"
      },
      "create_story_page/*/aspect_ratio_button": {
        "aspect_ratio_icon": "aspect_ratio.png",
        "background_color": "#80000000"
      },
      "create_story_page/*/story_hyperlink_button": {
        "hyperlink_button_icon": "hyperlink_button.png",
        "background_color": "#80000000"
      },
      "create_story_page/*/hyper_link": {
        "hyper_link_icon": "hyper_link.png",
        "background_color": "#80000000"
      },
      "create_story_page/*/share_story_button": {
        "share_icon": "share_story_button.png",
        "background_color": "#FFFFFF",
        "hide_avatar": false
      },
      "story_page/*/*": {},
      "story_page/*/progress_bar": {
        "progress_color": "#FFFFFF",
        "background_color": "#50FFFFFF"
      },
      "story_page/*/overflow_menu": {
        "overflow_menu_icon": "threeDot.png"
      },
      "story_page/*/close_button": {
        "close_icon": "close.png"
      },
      "story_page/*/story_impression_button": {
        "impression_icon": "impressionIcon.png"
      },
      "story_page/*/story_comment_button": {
        "comment_icon": "comment.png",
        "background_color": "#1234DD"
      },
      "story_page/*/story_reaction_button": {
        "reaction_icon": "like.png",
        "background_color": "#1243EE"
      },
      "story_page/*/create_new_story_button": {
        "create_new_story_icon": "plus.png",
        "background_color": "#1243EE"
      },
      "story_page/*/speaker_button": {
        "mute_icon": "mute.png",
        "unmute_icon": "unmute.png",
        "background_color": "#1243EE"
      },
      "*/edit_comment_component/*": {
      },
      "*/edit_comment_component/cancel_button": {
        "cancel_icon": "",
        "cancel_button_text": "cancel",
        "background_color": "#1243EE"
      },
      "*/edit_comment_component/save_button": {
        "save_icon": "",
        "save_button_text": "Save",
        "background_color": "#1243EE"
      },
      "*/hyper_link_config_component/*": {
        "theme": {
          "light": {
            "primary_color": "#1054DE",
            "secondary_color": "#292B32",
            "base_color": "#292b32",
            "base_shade1_color": "#636878",
            "base_shade2_color": "#898e9e",
            "base_shade3_color": "#a5a9b5",
            "base_shade4_color": "#ebecef",
            "alert_color": "#FA4D30",
            "background_color": "#FFFFFF",
          },
          "dark": {
            "primary_color": "#1054DE",
            "secondary_color": "#292B32",
            "base_color": "#ebecef",
            "base_shade1_color": "#a5a9b5",
            "base_shade2_color": "#6e7487",
            "base_shade3_color": "#40434e",
            "base_shade4_color": "#292b32",
            "alert_color": "#FA4D30",
            "background_color": "#191919",
          }
        }
      },
      "*/hyper_link_config_component/done_button": {
        "done_icon": "",
        "done_button_text": "Done",
        "background_color": "#1243EE"
      },
      "*/hyper_link_config_component/cancel_button": {
        "cancel_icon": "",
        "cancel_button_text": "Cancel"
      },
      "*/comment_tray_component/*": {
        "theme": {
          "light": {
            "primary_color": "#1054DE",
            "secondary_color": "#292B32",
            "base_color": "#292b32",
            "base_shade1_color": "#636878",
            "base_shade2_color": "#898e9e",
            "base_shade3_color": "#a5a9b5",
            "base_shade4_color": "#ebecef",
            "alert_color": "#FA4D30",
            "background_color": "#FFFFFF",
          },
          "dark": {
            "primary_color": "#1054DE",
            "secondary_color": "#292B32",
            "base_color": "#ebecef",
            "base_shade1_color": "#a5a9b5",
            "base_shade2_color": "#6e7487",
            "base_shade3_color": "#40434e",
            "base_shade4_color": "#292b32",
            "alert_color": "#FA4D30",
            "background_color": "#191919",
          }
        }
      },
      "*/story_tab_component/*": {
      },
      "*/story_tab_component/story_ring": {
        "progress_color": [
          "#339AF9",
          "#78FA58"
        ],
        "background_color": [
          "#EBECEF"
        ]
      },
      "*/story_tab_component/create_new_story_button": {
        "create_new_story_icon": "plus.png",
        "background_color": "#1243EE"
      },
      "*/*/close_button": {
        "close_icon": "close.png"
      }
    }
  }

  ```
</CodeGroup>

## Theme Application & Preferred Mode

Set the app's theme resolution strategy with `preferred_theme`:

| Value   | Behavior                       |
| ------- | ------------------------------ |
| default | Follow OS setting (light/dark) |
| light   | Force light regardless of OS   |
| dark    | Force dark regardless of OS    |

<Tip>
  Use `default` unless you have a business requirement to lock theme (e.g., brand guidelines for launch).
</Tip>

## Targeting Examples

<AccordionGroup>
  <Accordion title="Page-Level" icon="window">
    Override a whole screen. Great for hero / camera / immersive surfaces.

    <CodeGroup>
      ```json theme={null}
      "select_target_page/*/*": { "title": "Share To" }
      ```

      ```json theme={null}
      "camera_page/*/*": {
        "theme": { "light": { "primary_color": "#1D1234" } }
      }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Component-Level" icon="cube">
    Adjust reusable functional blocks everywhere.

    <CodeGroup>
      ```json theme={null}
      "*/edit_comment_component/*": { "theme": { "light": { "primary_color": "#1254DE" } } }
      ```

      ```json theme={null}
      "*/comment_tray_component/*": { "theme": { "dark": { "background_color": "#161A20" } } }
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Element-Level" icon="wand-magic-sparkles">
    Fine-grain tweak for single control.

    <CodeGroup>
      ```json theme={null}
      "story_page/*/story_comment_button": {
        "comment_icon": "comment.png",
        "background_color": "#1234DD"
      }
      ```

      ```json theme={null}
      "story_page/*/create_new_story_button": {
        "create_new_story_icon": "plus.png",
        "background_color": "#1243EE"
      }
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

## Reference: Allowed IDs

<Tabs>
  <Tab title="Pages">
    <CodeGroup>
      ```text theme={null}
      select_target_page/*/*
      camera_page/*/*
      create_story_page/*/*
      story_page/*/*
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Components">
    <CodeGroup>
      ```text theme={null}
      */edit_comment_component/*
      */comment_tray_component/*
      */story_tab_component/*
      */hyper_link_config_component/*
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Elements (Partial)">
    <CodeGroup>
      ```text theme={null}
      select_target_page/*/back_button
      camera_page/*/close_button
      create_story_page/*/back_button
      create_story_page/*/aspect_ratio_button
      create_story_page/*/story_hyperlink_button
      create_story_page/*/hyper_link
      create_story_page/*/share_story_button
      story_page/*/progress_bar
      story_page/*/overflow_menu
      story_page/*/close_button
      story_page/*/story_impression_button
      story_page/*/story_comment_button
      story_page/*/story_reaction_button
      story_page/*/create_new_story_button
      story_page/*/speaker_button
      */edit_comment_component/cancel_button
      */edit_comment_component/save_button
      */hyper_link_config_component/done_button
      */hyper_link_config_component/cancel_button
      */story_tab_component/story_ring
      */story_tab_component/create_new_story_button
      */*/close_button
      ```
    </CodeGroup>

    <Note>List may expand in future versions; avoid hard-coding validation exclusively to this snapshot.</Note>
  </Tab>
</Tabs>

## Exclusions

Remove (hide) specific elements by adding their path to `excludes`:

```json theme={null}
"excludes": [
  "create_story_page/*/aspect_ratio_button"
]
```

<Warning>
  Excluding structural elements (like a required close button) may degrade usability. Test flows after any exclusion.
</Warning>

## Precedence & Conflict Resolution

| Scenario                    | Winning Value              |
| --------------------------- | -------------------------- |
| Page vs Global              | Page override              |
| Component vs Page           | Component override         |
| Element vs Component        | Element override           |
| Multiple matches same level | Last in file (bottom-most) |

<Tip>
  Group related overrides together to prevent accidental shadowing by later blocks.
</Tip>

## Custom Config File (iOS)

<Info>
  <strong>iOS only.</strong> On Android, Flutter, and React Native, the config JSON is loaded differently per platform. On iOS, use <code>setConfigFile</code> to load a local JSON file from your app bundle at initialisation time.
</Info>

By default UIKit reads its bundled `config.json`. `setConfigFile` lets you supply a replacement from your own app bundle — useful for per-environment configs, white-label builds, or shipping your own defaults without modifying UIKit source.

### Implementation

<CodeGroup>
  ```swift iOS theme={null}
  import AmityUIKit4

  func setupUIKit() {
      AmityUIKit4Manager.setup(client: AmityUIKitManager.client)

      // Load a custom config.json from the app bundle
      if let filePath = Bundle.main.path(forResource: "AmityUIKitConfig", ofType: "json") {
          AmityUIKit4Manager.setConfigFile(filePath)
      }
  }
  ```
</CodeGroup>

<Tip>
  Call <code>setConfigFile</code> before presenting any UIKit view. Config values are read at render time — calling it after the first render may leave some components using the default config.
</Tip>

| Parameter  | Type     | Description                                                                                            |
| ---------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `filePath` | `String` | Absolute path to your `config.json` file. Use `Bundle.path(forResource:ofType:)` to resolve it safely. |

<Warning>
  If the file path is invalid or the JSON is malformed, UIKit silently falls back to its bundled defaults. Validate your JSON locally before shipping.
</Warning>

## Custom Asset Bundle (iOS)

<Info>
  <strong>iOS only.</strong> On Android, Flutter, and React Native, image overrides are handled through the <code>config.json</code> element keys (e.g. <code>"close\_icon": "close.png"</code>).
</Info>

In addition to `config.json`, iOS lets you supply a custom `Bundle` containing your own images. UIKit uses it as a fallback — your assets are loaded only for image names that do **not** exist in UIKit's own bundle.

<Warning>
  If you provide an image with the same name as a UIKit default asset, UIKit's copy wins. To replace an exposed icon, point its <code>config.json</code> key to a uniquely named file and provide that file in your custom bundle.
</Warning>

### How It Works

<Steps>
  <Step title="UIKit bundle checked first">
    If the image name exists in UIKit's own bundle, that version is always used.
  </Step>

  <Step title="Custom bundle as fallback">
    Only when the name is **not** found in UIKit's bundle does it fall through to your registered custom bundle.
  </Step>

  <Step title="Not found anywhere">
    If the image is absent from both bundles, UIKit renders a placeholder rather than crashing.
  </Step>
</Steps>

### Implementation

<Tabs>
  <Tab title="Basic Setup">
    Call `setCustomAssetBundle` once during app initialisation, after `AmityUIKit4Manager.setup(client:)`.

    <CodeGroup>
      ```swift iOS theme={null}
      import AmityUIKit4

      func setupUIKit() {
          AmityUIKit4Manager.setup(client: AmityUIKitManager.client)

          // Register your app's main bundle as the custom asset source
          AmityUIKit4Manager.setCustomAssetBundle(bundle: .main)
      }
      ```
    </CodeGroup>
  </Tab>

  <Tab title="With Config File">
    Combine bundle registration with a custom `config.json` so UIKit resolves icon keys to your asset names.

    <CodeGroup>
      ```swift iOS theme={null}
      func setupUIKit() {
          AmityUIKit4Manager.setup(client: AmityUIKitManager.client)

          if let filePath = Bundle.main.path(forResource: "AmityUIKitConfig", ofType: "json") {
              AmityUIKit4Manager.setConfigFile(filePath)
          }

          AmityUIKit4Manager.setCustomAssetBundle(bundle: .main)
      }
      ```

      ```json config.json theme={null}
      {
        "customizations": {
          "story_page/*/close_button": {
            "close_icon": "my_close_icon.png"
          }
        }
      }
      ```
    </CodeGroup>

    <Note>
      UIKit looks for <code>my\_close\_icon.png</code> in its own bundle first. Since that name does not exist there, it falls back to your custom bundle where the file is found.
    </Note>
  </Tab>

  <Tab title="Dedicated Bundle">
    For modular apps where assets live in a Swift Package or framework bundle, pass that bundle instead of `Bundle.main`.

    <CodeGroup>
      ```swift Swift Package theme={null}
      // Bundle.module is generated automatically by SwiftPM
      AmityUIKit4Manager.setCustomAssetBundle(bundle: .module)
      ```

      ```swift Framework theme={null}
      // Assets are inside a custom framework
      let assetBundle = Bundle(for: MyAssetProvider.self)
      AmityUIKit4Manager.setCustomAssetBundle(bundle: assetBundle)
      ```
    </CodeGroup>
  </Tab>
</Tabs>

<Tip>
  Register the bundle before any UIKit view is presented. Images are resolved at render time — late registration may cause some views to display the wrong asset.
</Tip>

### Troubleshooting

<AccordionGroup>
  <Accordion title="Image is not showing">
    Verify the asset is included in the bundle you registered:

    ```swift theme={null}
    let url = AmityUIKit4Manager.customAssetBundle?
        .url(forResource: "my_icon", withExtension: "png")
    print(url ?? "not found in custom bundle")
    ```

    Check that the file name, extension, and capitalisation match exactly what is referenced in `config.json`.
  </Accordion>

  <Accordion title="UIKit is displaying its default icon instead of mine">
    An asset with the same name in UIKit's bundle always wins. Rename your asset uniquely and update the matching `config.json` icon key to point to the new name.
  </Accordion>

  <Accordion title="Works in Debug but missing in Release">
    Ensure the image file appears under **Copy Bundle Resources** in Build Phases. Loose PNG/PDF files (not inside an `.xcassets` catalog) must be added manually.
  </Accordion>
</AccordionGroup>

## Best Practices

<CardGroup cols={2}>
  <Card title="Token First" icon="palette">Change global tokens before per-element overrides.</Card>
  <Card title="Progressive Specificity" icon="sitemap">Escalate from page → component → element only when needed.</Card>
  <Card title="Consistency" icon="check">Reuse colors; avoid random hex proliferation.</Card>
  <Card title="Auditable Changes" icon="file-lines">Document rationale in PR descriptions.</Card>
  <Card title="Dark Mode QA" icon="moon">Visually inspect every high-contrast surface.</Card>
  <Card title="Minimal Exclusions" icon="scissors">Prefer styling adjustments over removal.</Card>
</CardGroup>

## Overriding Navigation Behaviour

<Info>
  Navigation can be customized without forking by overriding the provided Behaviour classes. UIKit isolates navigation & event hooks into behaviour objects per page/component so you can inject custom flows (e.g., presenting a different creation screen, analytics, guards) while keeping styling overrides separate.
</Info>

### Behaviour Architecture

* Each major screen/component exposes a `Behaviour` (iOS) / `Behavior` (Android) class with overridable navigation methods.
* A top-level `AmityUIKit4Manager.behaviour` (iOS) / `AmityUIKit4Manager.behavior` (Android) aggregates these instances.
* Replace only the concrete behaviour you need; leave others at default.

### Override Example

<CodeGroup>
  ```swift iOS theme={null}
  // Custom behaviour for Story Tab component
  class CustomStoryTabComponentBehaviour: AmityStoryTabComponentBehaviour {
    override init() { super.init() }

    override func goToViewStoryPage(context: AmityStoryTabComponentBehaviour.Context) {
      // Custom navigation (e.g., analytics, gated modal, custom host)
    }

    override func goToCreateStoryPage(context: AmityStoryTabComponentBehaviour.Context) {
      // Present alternative creation UI or permission flow
    }
  }

  // Register once (e.g. after SDK init)
  func configureNavigationOverrides() {
    AmityUIKit4Manager.behaviour.storyTabComponentBehaviour = CustomStoryTabComponentBehaviour()
  }
  ```

  ```kotlin Android theme={null}
  // Custom behavior for Story Tab component
  class CustomStoryTabComponentBehavior : AmityStoryTabComponentBehavior() {

    override fun goToViewStoryPage(
      context: Context,
      type: AmityViewStoryPageType,
    ) {
      // Launch custom activity / compose destination
    }

    override fun goToCreateStoryPage(
      context: Context,
      targetId: String,
      targetType: AmityStory.TargetType
    ) {
      // Inject permission checks or route to experimental editor
    }
  }

  fun configureNavigationOverrides() {
    AmityUIKit4Manager.behavior.storyTabComponentBehavior = CustomStoryTabComponentBehavior()
  }
  ```

  ```dart Flutter theme={null}
  // Custom behavior for Message Bubble component
  class CustomMessageBubbleBehavior extends AmityMessageBubbleBehavior {
    
    @override
    void onAvatarTap(
      BuildContext context,
      String? avatarUrl,
      String? userId,
    ) {
      // Navigate to custom user profile or inject analytics
      if (userId != null) {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => CustomUserProfilePage(userId: userId),
          ),
        );
      }
    }
  }

  // Apply custom behavior in your app initialization
  class MyApp extends StatelessWidget {
    const MyApp({super.key});
    
    @override
    Widget build(BuildContext context) {
      configureNavigationOverrides();
      
      return AmityUIKitProvider(
        child: Builder(builder: (context) {
          return const HomePage();
        }),
      );
    }
    
    void configureNavigationOverrides() {
      AmityUIKit4Manager.behavior.messageBubbleBehavior = 
        CustomMessageBubbleBehavior();
    }
  }
  ```
</CodeGroup>

<Tip>
  Only override the specific methods you need; keep defaults for forward compatibility.
</Tip>

### Advanced iOS Navigation (Swipe Back & SwiftUI Hosting)

UIKit v4 pages are predominantly SwiftUI, embedded in `UINavigationController` via helper controllers:

| Class                                     | Purpose                                          |
| ----------------------------------------- | ------------------------------------------------ |
| `AmitySwiftUIHostingController`           | Wrap a single SwiftUI view                       |
| `AmitySwiftUIHostingNavigationController` | Provide a navigation stack hosting SwiftUI pages |

To preserve the interactive swipe‑to‑go‑back gesture while customizing appearance, UIKit globally overrides navigation gesture delegate methods. You can supply your own gesture behaviour logic via `AmitySwipeToBackGestureBehavior`.

<CodeGroup>
  ```swift iOS theme={null}
  // Default usage
  let defaultGestureBehavior = AmitySwipeToBackGestureBehavior()

  // Custom gesture policy
  class CustomSwipeBackGestureBehavior: AmitySwipeToBackGestureBehavior {
    override func gestureRecognizerShouldBegin(
      navigationController: UINavigationController,
      _ gestureRecognizer: UIGestureRecognizer
    ) -> Bool {
      // Example: disable swipe on root page or during modal overlay
      return navigationController.viewControllers.count > 1
    }

    override func gestureRecognizer(
      _ gestureRecognizer: UIGestureRecognizer,
      shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
    ) -> Bool {
      // Allow simultaneous recognition with horizontally scrollable carousels
      return true
    }
  }

  func configureGestureBehavior() {
    AmityUIKit4Manager.behaviour.swipeToBackGestureBehavior = CustomSwipeBackGestureBehavior()
  }
  ```
</CodeGroup>

<Note>
  Keep gesture logic lightweight—long operations here can block UI responsiveness.
</Note>

### When to Escalate

If navigation changes require altering internal view composition (e.g., embedding a new container architecture), consider moving to <a href="/uikit/customization/advanced-customization">Fork & Extend</a>. Simple route substitutions, analytics, permission gates, and gesture tweaks belong here.

## Next Steps

<CardGroup cols={2}>
  <Card title="Dynamic UI" href="/uikit/customization/dynamic-ui" icon="bolt">Remote token updates</Card>
  <Card title="Advanced Customization" href="/uikit/customization/advanced-customization" icon="code-fork">Fork & extend source</Card>
</CardGroup>
