Skip to content

Comments

Modernize and simplify frontend code#27

Open
guzus wants to merge 1 commit intomainfrom
claude/modernize-frontend-EPzGF
Open

Modernize and simplify frontend code#27
guzus wants to merge 1 commit intomainfrom
claude/modernize-frontend-EPzGF

Conversation

@guzus
Copy link
Collaborator

@guzus guzus commented Dec 14, 2025

  • Add comprehensive dark theme with CSS custom properties
  • Implement glassmorphism effects and modern card styles
  • Update all UI components with new design system
  • Add smooth animations and transitions
  • Reduce code complexity while maintaining functionality
  • Improve visual hierarchy with better typography and spacing

Summary by CodeRabbit

Release Notes

  • New Features

    • Dark theme styling with enhanced color palette
    • Entrance animations and visual transitions
    • Price type toggle in liquidity charts
    • Improved focus and hover interaction states
  • UI/UX Improvements

    • Redesigned card-based layouts with glass effects
    • Enhanced loading and error state visuals
    • Updated typography and color system throughout
    • Streamlined pool and token selection flows
    • Better organized dashboard layouts
  • Chores

    • Updated page title and metadata

✏️ Tip: You can customize this high-level summary in your review settings.

- Add comprehensive dark theme with CSS custom properties
- Implement glassmorphism effects and modern card styles
- Update all UI components with new design system
- Add smooth animations and transitions
- Reduce code complexity while maintaining functionality
- Improve visual hierarchy with better typography and spacing
@netlify
Copy link

netlify bot commented Dec 14, 2025

Deploy Preview for tel-on-chain ready!

Name Link
🔨 Latest commit e0a22ae
🔍 Latest deploy log https://app.netlify.com/projects/tel-on-chain/deploys/693e6effc8f4310008db03e8
😎 Deploy Preview https://deploy-preview-27--tel-on-chain.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 14, 2025

Walkthrough

This PR introduces a comprehensive design system overhaul for the tel-ui-web frontend, migrating from basic light theme styling to a cohesive dark theme with semantic color tokens, modernized UI components, and refined visual layouts across pages and components while simplifying state management in several areas.

Changes

Cohort / File(s) Summary
Design System & Theme
src/app/globals.css
Replaces light theme with dark theme palette; adds semantic color tokens (success, danger, warning, accent); introduces new CSS custom properties for surfaces, borders, shadows; adds card, glass effect, gradient, animation, and focus-ring utility classes; removes old dark-mode override block and adds universal scrollbar and selection styling.
Root Layout & Metadata
src/app/layout.tsx
Updates page metadata (title, description, keywords, authors); adds dark class to html element; adjusts body classes from antialiased bg-gray-50 to antialiased min-h-screen.
Main Pages
src/app/page.tsx, src/app/token-aggregate/page.tsx
Redesigns main page and token-aggregate page with new icons (BarChart3, Sparkles), removes statsMode state, updates pool selection flow, overhauls UI structure (header, hero, analysis sections), improves loading/error states, and applies new theme token-based styling throughout.
Data Visualization Components
src/components/LiquidityChart.tsx, src/components/StatsSummary.tsx
Redesigns LiquidityChart UI with card layout, price type toggle, inline slider, updated tooltip styling with CSS variables; refactors StatsSummary with computed buyPercentage, simplified card-based layout, aggregate data handling, and new Strongest Walls presentation.
List & Selection Components
src/components/PoolList.tsx, src/components/TokenSelector.tsx
Introduces DEX_NAMES mapping in PoolList; hardens chainId to 1; redesigns pool row layout with token-pair display and ExternalLink icons; removes helper functions; updates TokenSelector with minor formatting and UI refinements.
UI Component Library
src/components/ui/Button.tsx, src/components/ui/Input.tsx, src/components/ui/Pagination.tsx, src/components/ui/Select.tsx
Extends Button variant to include 'danger'; updates focus ring and active state styling with CSS variables; refactors Input, Select, and Pagination components to use var-based color tokens, new hover/focus states, and modernized layouts; compresses pagination range generation and switches Previous/Next to icon-only buttons.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Areas requiring extra attention:

  • StatsSummary.tsx — Verify buyPercentage computation logic is correct for both aggregate and non-aggregate modes; ensure aggregate fallbacks to undefined are handled gracefully in downstream rendering.
  • PoolList.tsx — Review the simplified token-pair formatting and optional chaining logic; confirm the DEX_NAMES mapping covers all expected pool types and that the active state styling correctly reflects pool selection.
  • LiquidityChart.tsx — Confirm price type toggle logic, scaleRange slider behavior, and CustomTooltip data rendering align with updated chart data structure; verify all color variable references resolve correctly.
  • globals.css — Validate that all new CSS custom properties are properly defined and that animation/utility classes function correctly across different browsers.

Possibly related PRs

Poem

🐰 Behold, a theme so dark and grand,
With cards and glass, we redesign the land,
CSS tokens dance in harmony's glow,
Buttons and inputs steal the show,
— From shadows deep, fresh beauty grows! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Modernize and simplify frontend code' accurately reflects the main changes across the entire changeset, which comprehensively modernize the UI with a dark theme, new design system, and simplified component logic.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/modernize-frontend-EPzGF

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (8)
tel-ui-web/src/components/ui/Button.tsx (1)

21-25: Inconsistent hover color for danger variant.

The danger variant uses a hardcoded hover:bg-red-600 while all other variants use CSS variables for hover states. For design system consistency, consider using a CSS variable like --danger-hover.

-            'bg-[var(--danger)] text-white hover:bg-red-600': variant === 'danger',
+            'bg-[var(--danger)] text-white hover:bg-[var(--danger-hover)]': variant === 'danger',

This would require adding --danger-hover to your CSS variables in globals.css.

tel-ui-web/src/components/TokenSelector.tsx (1)

39-43: Silent failure when addresses are invalid.

The guard if (!isValidAddress(...)) return; silently exits without user feedback. However, the form already has validation via react-hook-form (lines 97-98, 106-107). This guard is redundant since handleSubmit won't call onSubmit if validation fails, unless the form is submitted programmatically.

Consider removing this redundant check or, if kept for defensive purposes, adding user feedback.

  const onSubmit = (data: FormData) => {
-   if (!isValidAddress(data.token0) || !isValidAddress(data.token1)) return;
    onTokensChange(data.token0, data.token1);
    onFiltersChange({ chainId: data.chainId, dex: data.dex || undefined });
  };
tel-ui-web/src/components/ui/Pagination.tsx (1)

76-85: Inconsistent use of raw <button> instead of Button component.

The navigation arrows use the Button component, but page number buttons use raw <button> elements with inline styles. This creates inconsistency in focus states, accessibility features, and styling patterns.

Consider using the Button component with appropriate variant for page numbers to maintain consistency.

-              <button
-                onClick={() => onPageChange(page as number)}
-                className={`min-w-[32px] h-8 text-sm rounded-md transition-all ${
-                  currentPage === page
-                    ? 'bg-[var(--accent)] text-white'
-                    : 'text-[var(--foreground-muted)] hover:text-[var(--foreground)] hover:bg-[var(--surface)]'
-                }`}
-              >
-                {page}
-              </button>
+              <Button
+                variant={currentPage === page ? 'primary' : 'ghost'}
+                size="sm"
+                onClick={() => onPageChange(page as number)}
+                className="min-w-[32px]"
+              >
+                {page}
+              </Button>
tel-ui-web/src/components/PoolList.tsx (1)

27-32: Consider making chainId configurable.

The chainId is hardcoded to 1 (Ethereum mainnet). If multi-chain support is planned, consider accepting this as a prop or from a context provider.

tel-ui-web/src/app/globals.css (1)

99-118: Gradient utilities use hardcoded secondary colors.

The gradient definitions mix CSS variables with hardcoded hex values (e.g., #8b5cf6, #34d399, #f87171, #a78bfa). For consistency and easier theme adjustments, consider defining these as CSS variables.

 :root {
+  /* Gradient secondary colors */
+  --accent-gradient-end: #8b5cf6;
+  --success-gradient-end: #34d399;
+  --danger-gradient-end: #f87171;
   ...
 }

 .gradient-accent {
-  background: linear-gradient(135deg, var(--accent) 0%, #8b5cf6 100%);
+  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-gradient-end) 100%);
 }
tel-ui-web/src/components/StatsSummary.tsx (1)

157-191: Non-null assertions rely on surrounding conditionals.

The code uses ! assertions (e.g., displayData.strongestBuyLevel!.token1_liquidity) after conditional checks. While safe in this context, extracting the values in the condition would be more explicit:

{displayData.isAggregate && displayData.strongestBuyLevel && (
  <div className="...">
    {/* Use strongestBuyLevel directly without ! */}
    {formatNumber(displayData.strongestBuyLevel.token1_liquidity, { compact: true })}
  </div>
)}
tel-ui-web/src/app/token-aggregate/page.tsx (1)

197-206: StatsSummary receives zeroes for liquidity but relies on aggregate mode.

Passing totalBuyLiquidity={0} and totalSellLiquidity={0} works because mode="aggregate" causes StatsSummary to fetch its own data. However, this creates duplicate API calls since useTokenAggregateData is called both here and inside StatsSummary.

Consider either passing the already-fetched data to avoid duplicate requests, or calculating the totals here and passing them directly.

+const totalBuyLiquidity = data?.price_levels
+  ?.filter(level => level.side === 'Buy')
+  .reduce((sum, level) => sum + level.token1_liquidity, 0) ?? 0;
+
+const totalSellLiquidity = data?.price_levels
+  ?.filter(level => level.side === 'Sell')
+  .reduce((sum, level) => sum + level.token1_liquidity, 0) ?? 0;

 <StatsSummary
-  totalBuyLiquidity={0}
-  totalSellLiquidity={0}
+  totalBuyLiquidity={totalBuyLiquidity}
+  totalSellLiquidity={totalSellLiquidity}
   currentPrice={data.current_price}
   token0Symbol={data.token0.symbol}
   token1Symbol={data.token1.symbol}
-  mode="aggregate"
-  tokenAddress={submittedToken}
+  mode="pair"
   chainId={1}
 />
tel-ui-web/src/components/LiquidityChart.tsx (1)

132-151: Consider extracting the toggle button to reduce duplication.

The price type toggle buttons share identical structure with only the value and label differing. Consider extracting this pattern into a reusable component or using a mapping approach.

Apply this diff to reduce duplication:

-          <div className="flex items-center gap-2 p-1 rounded-lg bg-[var(--surface)]">
-            <button
-              onClick={() => onPriceTypeChange('wall')}
-              className={`px-3 py-1.5 text-xs font-medium rounded-md transition-all ${
-                priceType === 'wall'
-                  ? 'bg-[var(--accent)] text-white'
-                  : 'text-[var(--foreground-muted)] hover:text-[var(--foreground)]'
-              }`}
-            >
-              Wall Price
-            </button>
-            <button
-              onClick={() => onPriceTypeChange('current')}
-              className={`px-3 py-1.5 text-xs font-medium rounded-md transition-all ${
-                priceType === 'current'
-                  ? 'bg-[var(--accent)] text-white'
-                  : 'text-[var(--foreground-muted)] hover:text-[var(--foreground)]'
-              }`}
-            >
-              Current
-            </button>
-          </div>
+          <div className="flex items-center gap-2 p-1 rounded-lg bg-[var(--surface)]">
+            {(['wall', 'current'] as const).map((type) => (
+              <button
+                key={type}
+                onClick={() => onPriceTypeChange(type)}
+                className={`px-3 py-1.5 text-xs font-medium rounded-md transition-all ${
+                  priceType === type
+                    ? 'bg-[var(--accent)] text-white'
+                    : 'text-[var(--foreground-muted)] hover:text-[var(--foreground)]'
+                }`}
+              >
+                {type === 'wall' ? 'Wall Price' : 'Current'}
+              </button>
+            ))}
+          </div>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93ea2d9 and e0a22ae.

📒 Files selected for processing (12)
  • tel-ui-web/src/app/globals.css (1 hunks)
  • tel-ui-web/src/app/layout.tsx (2 hunks)
  • tel-ui-web/src/app/page.tsx (4 hunks)
  • tel-ui-web/src/app/token-aggregate/page.tsx (2 hunks)
  • tel-ui-web/src/components/LiquidityChart.tsx (4 hunks)
  • tel-ui-web/src/components/PoolList.tsx (2 hunks)
  • tel-ui-web/src/components/StatsSummary.tsx (2 hunks)
  • tel-ui-web/src/components/TokenSelector.tsx (5 hunks)
  • tel-ui-web/src/components/ui/Button.tsx (3 hunks)
  • tel-ui-web/src/components/ui/Input.tsx (2 hunks)
  • tel-ui-web/src/components/ui/Pagination.tsx (2 hunks)
  • tel-ui-web/src/components/ui/Select.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
tel-ui-web/src/components/ui/Pagination.tsx (1)
tel-ui-web/src/components/ui/Button.tsx (1)
  • Button (51-51)
tel-ui-web/src/components/LiquidityChart.tsx (1)
tel-ui-web/src/lib/utils.ts (2)
  • formatNumber (7-35)
  • formatPrice (37-79)
tel-ui-web/src/components/ui/Input.tsx (1)
tel-ui-web/src/lib/utils.ts (1)
  • cn (3-5)
tel-ui-web/src/components/ui/Select.tsx (1)
tel-ui-web/src/lib/utils.ts (1)
  • cn (3-5)
tel-ui-web/src/components/PoolList.tsx (3)
tel-ui-web/src/hooks/usePoolData.ts (1)
  • usePoolData (23-77)
tel-ui-web/src/types/api.ts (1)
  • Pool (53-63)
tel-ui-web/src/components/ui/Pagination.tsx (1)
  • Pagination (14-101)
tel-ui-web/src/components/TokenSelector.tsx (5)
tel-ui-web/src/lib/utils.ts (1)
  • isValidAddress (86-88)
tel-ui-web/src/lib/constants.ts (1)
  • SUPPORTED_CHAINS (3-24)
tel-ui-web/src/components/ui/Button.tsx (1)
  • Button (51-51)
tel-ui-web/src/components/ui/Input.tsx (1)
  • Input (42-42)
tel-ui-web/src/components/ui/Select.tsx (1)
  • Select (53-53)
tel-ui-web/src/app/token-aggregate/page.tsx (3)
tel-ui-web/src/hooks/useTokenAggregateData.ts (1)
  • useTokenAggregateData (18-65)
tel-ui-web/src/components/LiquidityChart.tsx (1)
  • LiquidityChart (89-205)
tel-ui-web/src/components/StatsSummary.tsx (1)
  • StatsSummary (22-196)
🔇 Additional comments (22)
tel-ui-web/src/components/ui/Select.tsx (1)

20-44: LGTM! Consistent styling with the new design system.

The Select component styling aligns well with the Input component, using the same CSS variables and transition patterns. The error state handling and hover effects are appropriately implemented.

tel-ui-web/src/components/ui/Button.tsx (1)

38-41: Clean loading spinner implementation.

The spinner uses currentColor appropriately, ensuring it inherits the correct color from each button variant. The opacity layering creates a nice visual effect.

tel-ui-web/src/app/layout.tsx (2)

22-22: Hardcoded dark mode ignores user system preferences.

The className="dark" is hardcoded, which means users who prefer light mode (via OS settings) won't have their preference respected. If this is intentional (dark-only design), this is fine. Otherwise, consider respecting prefers-color-scheme or providing a toggle.

Is the intent to have a dark-only theme, or should there be light mode support in the future?


4-14: Metadata updates look good.

The updated title, keywords, and OpenGraph data better reflect the analytics focus of the application.

tel-ui-web/src/components/TokenSelector.tsx (1)

61-71: Styling updates align with the design system.

The use of CSS variables and consistent class naming (e.g., card, text-[var(--foreground)], text-[var(--accent)]) integrates well with the broader theming changes.

tel-ui-web/src/components/ui/Input.tsx (1)

14-33: LGTM! Consistent theming with Select component.

The Input component styling mirrors the Select component exactly, ensuring a cohesive form control appearance. The placeholder opacity modifier (/50) and error state handling are well-implemented.

tel-ui-web/src/components/ui/Pagination.tsx (1)

22-46: Page number generation logic is correct.

The algorithm properly handles edge cases with dots for ellipsis, deduplicates entries, and guards against small page counts. The early return on line 51 prevents unnecessary rendering.

tel-ui-web/src/app/page.tsx (3)

43-80: Header implementation looks good.

The sticky header with glassmorphism effect (glass class) is well-implemented. The conditional rendering of the back button based on currentView and the refresh functionality with loading state are properly handled.


113-151: Welcome state UI is clean and informative.

The empty state provides clear guidance with visual hierarchy. The feature list effectively communicates the application's capabilities.


177-203: Error handling provides good UX with recovery option.

The error state includes clear messaging, visual indicators, and a retry button. Consider wrapping the error display with an error boundary at a higher level for uncaught exceptions.

tel-ui-web/src/components/PoolList.tsx (2)

34-41: Client-side filtering on paginated data may yield incomplete results.

The search filter operates only on the currently fetched page of pools. Users searching for a specific pool may not find it if it's on a different page. Consider either fetching all pools for client-side search or implementing server-side search.


117-147: Pool list UI and selection state look good.

The pool row implementation with hover states, selection highlighting, and truncated address display provides a clean UX. The DEX badge using DEX_NAMES mapping with fallback to raw value is a nice touch.

tel-ui-web/src/app/globals.css (2)

3-33: Well-structured design token system.

The CSS custom properties are organized logically with clear naming conventions. The semantic color tokens (success, danger, warning) and the subtle variants with rgba transparency provide a flexible theming foundation.


126-152: Animations are appropriately subtle.

The animation durations (0.3s-0.4s) and easing functions are well-chosen for a professional feel. The pulse-glow infinite animation is used sparingly which is good for performance.

tel-ui-web/src/components/StatsSummary.tsx (2)

40-79: Data transformation logic is well-structured.

The useMemo correctly handles both aggregate and pair modes with appropriate dependency array. The conditional data fetching pattern (passing undefined to skip) integrates cleanly with the hook design.


137-149: Ratio bar provides clear visual feedback.

The buy/sell ratio visualization with the progress bar and numeric display effectively communicates the liquidity balance. The transition animation adds polish.

tel-ui-web/src/app/token-aggregate/page.tsx (2)

37-46: Chart data transformation is correct.

The filtering for valid price bounds and mapping to the expected chart format handles the data appropriately. The fallback to empty array prevents runtime errors.


99-111: Example tokens provide good discoverability.

The quick-access buttons for common tokens improve UX by letting users explore without knowing addresses. The click handler correctly sets both input and submitted state.

tel-ui-web/src/components/LiquidityChart.tsx (4)

36-70: LGTM! Clean tooltip modernization.

The inline type annotation is appropriate for the Recharts tooltip, and the updated styling with CSS variables aligns well with the new design system. The simplified "Buy"/"Sell" labels improve clarity.


72-87: Axis tick styling updated correctly.

The color tokens and typography updates align with the new design system. The positioning and rotation values are appropriate for the chart layout.


90-104: Data processing logic is sound.

The state management and memoized data processing correctly filter and sort based on the scale range. The useMemo dependencies are complete and appropriate.


171-202: CSS custom properties are properly defined.

All CSS variables used in the chart (--foreground-muted, --border, --success, --danger, --accent, --foreground) are correctly defined in tel-ui-web/src/app/globals.css and will render as expected.

Comment on lines +112 to +118
/* Text gradient */
.text-gradient {
background: linear-gradient(135deg, var(--accent) 0%, #a78bfa 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

CSS background-clip text browser support compatibility 2024 2025

💡 Result:

Short answer

  • background-clip: text is widely usable in modern browsers in 2024–2025, but you should still include the WebKit-prefixed form for best cross‑browser coverage (Safari, older Chrome/Edge builds). Use color: transparent. [1][2][3]

Compatibility summary (Dec 2025)

  • Safari (macOS & iOS): supports (has long required -webkit- prefix in many releases). [1][2]
  • Chrome / Edge / Chrome for Android / Samsung Internet: supported in modern releases (unprefixed in recent Chromium; older versions relied on -webkit-). [2][3]
  • Firefox: historically differed — behavior and support has changed across versions; test target Firefox versions and use fallbacks/feature queries. [1][2][3]

Recommended pattern

  • Use both forms and a feature query/fallback:

    .text-clip {
    background: linear-gradient(90deg,#f06,#4a90e2);
    color: transparent;
    -webkit-background-clip: text;
    background-clip: text;
    }

  • Optional feature query to provide alternate styling when not supported:

    @supports not ((-webkit-background-clip: text) or (background-clip: text)) {
    .text-clip { color: #f06; /* fallback */ }
    }

Sources
[1] MDN — background-clip (docs + compatibility).
[2] Web Platform Features Explorer — background-clip: text (status & notes).
[3] Can I use — background-clip (support table).


🌐 Web query:

-webkit-text-fill-color transparent text selection accessibility issues

💡 Result:

Short answer

  • Using -webkit-text-fill-color: transparent with -webkit-background-clip: text (or background-clip: text) can make text selection invisible or low‑contrast for some users because the fill remains transparent when selected unless you explicitly override selection styles. It’s also a non‑standard WebKit property with browser-support and rendering quirks to watch. [1][2][3][4]

Why this causes accessibility issues

  • Transparent fill + clipped background = the visible color is the background image/gradient; when a user selects text the browser’s selection styling may not change that clipped background, leaving selected text unreadable (insufficient contrast) or completely invisible. That breaks WCAG contrast requirements for selected text. [1][2]
  • -webkit-text-fill-color is WebKit-specific and historically had inconsistent behavior across engines and versions (rendering artifacts, differing precedence vs color). If a browser doesn’t support background-clip:text, using color: transparent will simply hide text. [2][3][5]

How to fix / mitigate (brief)

  • Provide a safe fallback: use feature queries and fallbacks so non‑supporting browsers get a normal readable color. Example: @supports (background-clip: text) { ... } else use a plain color. [2]
  • Override ::selection to set readable selected text and selection background (ensure contrast ratio >= WCAG). Many browsers accept -webkit-text-fill-color inside ::selection, so explicitly set selection text color (not transparent). Example approach: ::selection { background: #xxx; color: #yyy; -webkit-text-fill-color: #yyy; }. That restores visibility when selected. [1][4]
  • Test across browsers (Safari/Chrome/Edge/Firefox) and assistive tech; avoid relying solely on non‑standard properties for critical content. [2][3]

References

  • MDN ::selection — notes on allowed properties and accessibility/contrast guidance. [1]
  • WebKit blog: background-clip:text history and recommended feature-query/fallback patterns. [2]
  • Stack Overflow discussion on -webkit-text-fill-color vs color, browser behavior and why people use the prefixed property. [3]
  • Practical example showing overriding selection to restore readable selected text. [4]

Sources
[1] MDN ::selection (CSS) — selection styling and accessibility.
[2] WebKit blog — background-clip:text and guidance (feature queries / fallbacks).
[3] StackOverflow threads on -webkit-text-fill-color and compatibility/behavior.
[4] Practical tutorial / article demonstrating ::selection override to fix selection visibility.


🌐 Web query:

CSS background-clip text fallback accessibility WCAG guidelines

💡 Result:

Short answer: using background-clip: text (text filled with an image/gradient) is treated like "text over an image" or "image of text" for WCAG — it must remain readable and meet the WCAG contrast and non‑text rules, and you must provide an accessible fallback if the decorative effect is unavailable. Key requirements and practical fallbacks:

What WCAG requires (high level)

  • Text over images/gradients must meet the WCAG contrast rules (1.4.3 and, for higher level, 1.4.6). Test the actual text appearance against the worst-case background area. [1][2][3]
  • If text is effectively an "image of text" (purely decorative headings, logos, or text conveyed only visually), follow the Images of Text guidance: don’t use images of text for essential content; provide text alternatives or use real text styled for accessibility. [4][5]

Accessibility risks with background-clip: text

  • The visible fill is an image/gradient that can vary under the glyphs, making contrast unpredictable and harder to test. Automated tools may flag failures if any glyph area falls below required contrast. [1][6]
  • Some user agents or high‑contrast modes may ignore background images/CSS effects, which can hide the fill and change appearance; ensure content still remains readable. [3]

Practical fallbacks and fixes

  • Provide a solid-color or high-contrast text fallback for UAs that don't support the effect:
    • e.g. set color to an accessible color and then override with background/clip when supported. Use feature detection (e.g. @supports(-webkit-background-clip: text) ) or progressive enhancement. [MDN guidance; see example below]. [2]
  • Ensure the fallback (or combination of background + text outline/overlay) meets WCAG contrast (4.5:1 for normal text, 3:1 for large). If the clipped background has variable color, add a semi-opaque overlay behind the text area or a text-stroke/shadow to guarantee contrast against the worst-case background. [1][3][6]
  • Avoid using the effect for essential information or interactive controls. If it’s decorative, mark it as decorative (so it isn’t relied on by AT) or ensure the content is available as plain accessible text. [4]
  • Test visually on real content and across the worst-case parts of the background (not just an average color). Use contrast tools and manual checks (including high-contrast modes and UA text-only rendering). The W3C ACT rules and testing guidance explain how to test text-over-image cases. [7][5]

Minimal code pattern (concept)

  • Provide accessible fallback color, then enable clipped fill when supported:
    • base: color: #111; /* accessible fallback */
    • @supports (background-clip: text) { background: linear-gradient(...); -webkit-background-clip: text; color: transparent; }
    • If needed add: text-shadow or -webkit-text-stroke and/or a semi-opaque overlay to ensure contrast for all parts of the glyphs.

References

  1. WCAG understanding for Contrast (1.4.3 / 1.4.6) and contrast testing guidance. [W3C / Understanding Contrast]. [1]
  2. MDN accessibility guide on color contrast and readability (text over images/gradients). [2]
  3. WCAG Techniques: using CSS for decorative images and notes about background images being ignored by user agents. [3]
  4. W3C Images of Text guidance / ACT rule (Images of Text & when images-of-text are allowed). [4][7]
  5. W3C ACT rule: "Text has minimum contrast" and "Images of text" that cover testing rules for text over backgrounds. [7]
  6. Practical guidance on testing and fixing text-over-image contrast (developer/accessibility guides). [6]

If you want, I can:

  • give a short tested CSS snippet with feature detection and a11y-safe fallback; or
  • check a specific gradient/image + font size you plan to use for contrast and propose exact color/overlay values.

Add accessible fallback and ::selection styling for text gradient.

The current gradient text technique has two accessibility issues:

  1. Selected text is invisible — When users select the text, -webkit-text-fill-color: transparent causes selected text to become unreadable or invisible because the selection styling doesn't properly override the transparent fill. Add ::selection override to restore contrast:

    .text-gradient::selection {
      background: [selection-bg-color];
      -webkit-text-fill-color: [readable-text-color];
    }
  2. Missing fallback for unsupported browsers — Browsers without background-clip: text support (and some Firefox versions) will render invisible text. Add a feature query with a fallback color:

    .text-gradient {
      color: [accessible-fallback-color]; /* fallback */
    }
    @supports (background-clip: text) {
      .text-gradient {
        background: linear-gradient(135deg, var(--accent) 0%, #a78bfa 100%);
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
      }
    }

Ensure the fallback color meets WCAG contrast ratio (4.5:1 for normal text).

🤖 Prompt for AI Agents
In tel-ui-web/src/app/globals.css around lines 112 to 118, the .text-gradient
rule makes selected text invisible and provides no fallback for browsers lacking
background-clip:text; fix by adding a .text-gradient::selection rule that
restores a readable text color and a readable selection background, and add a
fallback color on .text-gradient plus an @supports (background-clip: text) block
that applies the gradient, background-clip and transparent color only when
supported; ensure the fallback color meets WCAG 4.5:1 contrast for normal text.

Comment on lines +12 to +18
const EXAMPLE_TOKENS = [
{ name: 'WETH', address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' },
{ name: 'LINK', address: '0x514910771AF9Ca656af840dff83E8264EcF986CA' },
{ name: 'UNI', address: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984' },
{ name: 'PEPE', address: '0x6982508145454ce325ddbe47a25d4ec3d2311933' },
{ name: 'WBTC', address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599' },
];
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent address checksumming in EXAMPLE_TOKENS.

The PEPE token address is lowercase while others are checksummed. For consistency and to avoid potential issues with case-sensitive address comparisons, use consistent checksummed addresses.

 const EXAMPLE_TOKENS = [
   { name: 'WETH', address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' },
   { name: 'LINK', address: '0x514910771AF9Ca656af840dff83E8264EcF986CA' },
   { name: 'UNI', address: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984' },
-  { name: 'PEPE', address: '0x6982508145454ce325ddbe47a25d4ec3d2311933' },
+  { name: 'PEPE', address: '0x6982508145454Ce325dDbE47a25d4ec3d2311933' },
   { name: 'WBTC', address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599' },
 ];
🤖 Prompt for AI Agents
tel-ui-web/src/app/token-aggregate/page.tsx around lines 12-18: the PEPE token
address is lowercase while the others are EIP-55 checksummed; replace the PEPE
address with its checksummed form and ensure all entries use checksummed
addresses (you can normalize/validate each address programmatically using
ethers.utils.getAddress or a similar library before committing).

Comment on lines +161 to +163
<Button variant="outline" size="sm" onClick={() => setSubmittedToken(submittedToken)} className="mt-3">
Try Again
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Retry button won't trigger a refetch.

Setting setSubmittedToken(submittedToken) with the same value won't cause React to re-render or the useTokenAggregateData hook to refetch, since the state value doesn't change. Use the hook's refetch function instead.

-const { data, loading, error } = useTokenAggregateData({
+const { data, loading, error, refetch } = useTokenAggregateData({
   tokenAddress: submittedToken || undefined,
   dex: 'all',
   chainId: 1,
 });

 ...

-<Button variant="outline" size="sm" onClick={() => setSubmittedToken(submittedToken)} className="mt-3">
+<Button variant="outline" size="sm" onClick={() => refetch()} className="mt-3">
   Try Again
 </Button>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In tel-ui-web/src/app/token-aggregate/page.tsx around lines 161-163, the "Try
Again" button calls setSubmittedToken(submittedToken) which does nothing because
the state value doesn't change; instead, import or destructure the refetch
function from useTokenAggregateData and call refetch in the button onClick (or,
if using react-query, call the queryClient refetch method) so the hook actually
re-fetches; update the button to call refetch and ensure refetch is defined
where the hook is used.

Comment on lines +157 to +165
<input
type="range"
min="5"
max="50"
step="5"
value={scaleRange}
onChange={(e) => setScaleRange(Number(e.target.value))}
className="w-20"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add accessibility label to range input.

The range slider lacks proper labeling for screen readers. Without an accessible label, users relying on assistive technologies cannot understand the purpose of this control.

Apply this diff to add an accessible label:

           <div className="flex items-center gap-2">
             <span className="text-xs text-[var(--foreground-muted)]">±{scaleRange}%</span>
             <input
               type="range"
               min="5"
               max="50"
               step="5"
               value={scaleRange}
               onChange={(e) => setScaleRange(Number(e.target.value))}
               className="w-20"
+              aria-label="Scale range percentage"
             />
           </div>

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In tel-ui-web/src/components/LiquidityChart.tsx around lines 157 to 165 the
range input lacks an accessible label for screen readers; add an explicit label
by either assigning an id to the input and rendering a corresponding <label
htmlFor="..."> (can be visually hidden via CSS) or by adding an
aria-label/aria-labelledby attribute (e.g., aria-label="Chart scale" or
aria-labelledby pointing to a hidden label) so assistive technologies convey the
control's purpose; keep the existing props
(min/max/step/value/onChange/className) unchanged.

Comment on lines +149 to +157
{pools.length === itemsPerPage && (
<Pagination
currentPage={currentPage}
totalPages={currentPage + 1}
onPageChange={setCurrentPage}
totalItems={pools.length}
itemsPerPage={itemsPerPage}
/>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Pagination logic is inaccurate and may cause confusion.

Setting totalPages to currentPage + 1 is a workaround that doesn't reflect actual pagination state. This means users always see a "next page" button when the current page is full, even if no more data exists. The totalItems is also set to pools.length (current page items) rather than the actual total.

Consider fetching total count from the API or implementing cursor-based pagination. If server-side total isn't available, at least disable "next" when fetched items are fewer than itemsPerPage.

-          {pools.length === itemsPerPage && (
+          {filteredPools.length > 0 && (
             <Pagination
               currentPage={currentPage}
-              totalPages={currentPage + 1}
+              totalPages={pools.length === itemsPerPage ? currentPage + 1 : currentPage}
               onPageChange={setCurrentPage}
-              totalItems={pools.length}
+              totalItems={currentPage * itemsPerPage}
               itemsPerPage={itemsPerPage}
+              showItemsInfo={false}
             />
           )}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In tel-ui-web/src/components/PoolList.tsx around lines 149–157, the Pagination
props are incorrect: totalPages is hardcoded to currentPage + 1 and totalItems
is set to pools.length (only current page items). Replace these with a real
total count if the API provides one (set totalItems = totalCount and totalPages
= Math.ceil(totalCount / itemsPerPage)); if the API does not return a total, do
not fake totalPages — instead pass a hasNext/disableNext flag computed as
(pools.length === itemsPerPage) ? true : false (or set totalPages = currentPage
+ (pools.length === itemsPerPage ? 1 : 0)), and update the Pagination component
to accept/handle totalItems optional or a hasNext prop so the Next button is
disabled when fetched items < itemsPerPage; preferably request and use
totalCount from the backend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants