Skip to content

feat: make stop icon and loading cursor customizable via CSS variables#2866

Merged
hayescode merged 2 commits intoChainlit:mainfrom
nzjrs:feat/customizable-loading-indicators
Apr 2, 2026
Merged

feat: make stop icon and loading cursor customizable via CSS variables#2866
hayescode merged 2 commits intoChainlit:mainfrom
nzjrs:feat/customizable-loading-indicators

Conversation

@nzjrs
Copy link
Copy Markdown
Contributor

@nzjrs nzjrs commented Mar 30, 2026

Replace hardcoded SVG/Tailwind with CSS mask-image rendering so the shape, color, size, and animation of the stop icon and loading cursor can be overridden via theme.json or custom CSS.

CSS variables exposed:

--stop-icon-mask SVG data URI for icon shape
--stop-icon-color defaults to currentColor
--stop-icon-animation defaults to none

--loading-cursor-mask SVG data URI (omit for default circle)
--loading-cursor-color defaults to hsl(var(--foreground))
--loading-cursor-size defaults to 0.875rem
--loading-cursor-animation defaults to pulse

Simple example (breathing pulse on both):

  :root {
      --stop-icon-animation: my-pulse 2s ease-in-out infinite;
      --loading-cursor-animation: my-breathe 2s ease-in-out infinite;
      --loading-cursor-size: 1.25rem;
  }
  @keyframes my-pulse {
      0%, 100% { opacity: 1; transform: scale(1); }
      50% { opacity: 0.5; transform: scale(0.92); }
  }
  @keyframes my-breathe {
      0%, 100% { opacity: 1; transform: scale(1); }
      50% { opacity: 0.35; transform: scale(0.85); }
  }

Advanced example (custom icon shape and color):

  :root {
      --stop-icon-animation: stop-pulse 0.8s ease-in-out infinite;
      --loading-cursor-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M9 18l6-6-6-6' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E");
      --loading-cursor-color: hsl(120, 60%, 55%);
      --loading-cursor-animation: chevron-spin 0.9s linear infinite;
  }
  @keyframes stop-pulse {
      0%, 100% { opacity: 1; transform: scale(1); }
      50% { opacity: 0.2; transform: scale(1.35); }
  }
  @keyframes chevron-spin {
      from { transform: rotate(0deg); }
      to { transform: rotate(360deg); }
  }

Replace hardcoded SVG/Tailwind with CSS mask-image rendering so
the shape, color, size, and animation of the stop icon and loading
cursor can be overridden via theme.json or custom CSS.

CSS variables exposed:

  --stop-icon-mask        SVG data URI for icon shape
  --stop-icon-color       defaults to currentColor
  --stop-icon-animation   defaults to none

  --loading-cursor-mask   SVG data URI (omit for default circle)
  --loading-cursor-color  defaults to hsl(var(--foreground))
  --loading-cursor-size   defaults to 0.875rem
  --loading-cursor-animation  defaults to pulse

Simple example (breathing pulse on both):

  :root {
      --stop-icon-animation: my-pulse 2s ease-in-out infinite;
      --loading-cursor-animation: my-breathe 2s ease-in-out infinite;
      --loading-cursor-size: 1.25rem;
  }
  @Keyframes my-pulse {
      0%, 100% { opacity: 1; transform: scale(1); }
      50% { opacity: 0.5; transform: scale(0.92); }
  }
  @Keyframes my-breathe {
      0%, 100% { opacity: 1; transform: scale(1); }
      50% { opacity: 0.35; transform: scale(0.85); }
  }

Advanced example (custom icon shape and color):

  :root {
      --stop-icon-animation: stop-pulse 0.8s ease-in-out infinite;
      --loading-cursor-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M9 18l6-6-6-6' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E");
      --loading-cursor-color: hsl(120, 60%, 55%);
      --loading-cursor-animation: chevron-spin 0.9s linear infinite;
  }
  @Keyframes stop-pulse {
      0%, 100% { opacity: 1; transform: scale(1); }
      50% { opacity: 0.2; transform: scale(1.35); }
  }
  @Keyframes chevron-spin {
      from { transform: rotate(0deg); }
      to { transform: rotate(360deg); }
  }
@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. enhancement New feature or request frontend Pertains to the frontend. labels Mar 30, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 3 files

hayescode
hayescode previously approved these changes Apr 1, 2026
Copy link
Copy Markdown
Contributor

@hayescode hayescode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed via Codex

LGTM.

@dokterbob dokterbob enabled auto-merge April 1, 2026 16:06
@hayescode hayescode dismissed their stale review April 1, 2026 16:33

ci failing

@hayescode
Copy link
Copy Markdown
Contributor

@nzjrs please fix CI then we're good to merge!

The previous commit replaced Tailwind's animate-pulse class with a
custom loading-cursor CSS class on BlinkingCursor.tsx, but the Cypress
test still used .animate-pulse as its selector to detect the cursor.
auto-merge was automatically disabled April 2, 2026 10:12

Head branch was pushed to by a user without write access

@hayescode hayescode added this pull request to the merge queue Apr 2, 2026
Merged via the queue into Chainlit:main with commit fa7bc09 Apr 2, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request frontend Pertains to the frontend. size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants