Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions e2e/fixtures/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export type Theme = 'system' | 'light' | 'dark'
export interface ThemeTestFixtures {
/** Select a theme from the dropdown */
selectTheme: (theme: Theme) => Promise<void>
/** Get a computed CSS variable value from the [data-wallet-ui] element */
/** Get a computed CSS variable value from the [data-wallet-theme] element */
getComputedCSSVariable: (variable: string) => Promise<string>
/** Wait for theme transition to complete */
waitForThemeStable: () => Promise<void>
Expand All @@ -61,12 +61,12 @@ export interface ThemeTestFixtures {
}

/**
* Get a computed CSS variable value from the [data-wallet-ui] element
* Get a computed CSS variable value from the [data-wallet-theme] element
*/
async function getCSSVariable(page: Page, variable: string): Promise<string> {
return page.evaluate((varName) => {
const el = document.querySelector('[data-wallet-ui]')
if (!el) throw new Error('Could not find [data-wallet-ui] element')
const el = document.querySelector('[data-wallet-theme]')
if (!el) throw new Error('Could not find [data-wallet-theme] element')
return getComputedStyle(el).getPropertyValue(varName).trim()
}, variable)
}
Expand Down Expand Up @@ -99,7 +99,7 @@ export const test = base.extend<ThemeTestFixtures>({
await page
.waitForFunction(
() => {
const elements = document.querySelectorAll('[data-wallet-ui] *')
const elements = document.querySelectorAll('[data-wallet-theme] *')
return Array.from(elements).every((el) => {
const style = getComputedStyle(el)
return (
Expand Down
4 changes: 2 additions & 2 deletions e2e/tests/customization/css-overrides.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { test, expect } from '@playwright/test'
test.describe('CSS Customization Patterns', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
})

test('page loads with all customization examples', async ({ page }) => {
Expand Down Expand Up @@ -126,7 +126,7 @@ test.describe('CSS Customization Patterns', () => {
test.describe('Theme Switching', () => {
test('amber theme changes color on dark mode toggle', async ({ page }) => {
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')

const amberSection = page.locator('section').filter({ hasText: '6. Theme-Aware Customization' })
const button = amberSection.locator('[data-wallet-button]')
Expand Down
12 changes: 6 additions & 6 deletions e2e/tests/theme/css-variables.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ test.describe('Portal-Critical CSS Variables', () => {
test.describe('Light Mode', () => {
test.beforeEach(async ({ page, selectTheme, waitForThemeStable }) => {
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
await selectTheme('light')
await waitForThemeStable()
})
Expand Down Expand Up @@ -41,8 +41,8 @@ test.describe('Portal-Critical CSS Variables', () => {
await page.waitForTimeout(300)

// Get CSS variables from the portal-rendered element
// The portal wraps content in a div with data-wallet-ui attribute
const portalElement = page.locator('#wallet-dialog-portal [data-wallet-ui]')
// The portal wraps content in a div with data-wallet-theme attribute
const portalElement = page.locator('#wallet-dialog-portal [data-wallet-theme]')

const bgColor = await portalElement.evaluate((el) => {
return getComputedStyle(el).getPropertyValue('--wui-color-bg').trim()
Expand All @@ -59,7 +59,7 @@ test.describe('Portal-Critical CSS Variables', () => {
test.describe('Dark Mode', () => {
test.beforeEach(async ({ page, selectTheme, waitForThemeStable }) => {
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
await selectTheme('dark')
await waitForThemeStable()
})
Expand Down Expand Up @@ -94,8 +94,8 @@ test.describe('Portal-Critical CSS Variables', () => {
await page.waitForTimeout(300)

// Get CSS variables from the portal-rendered element
// The portal wraps content in a div with data-wallet-ui attribute
const portalElement = page.locator('#wallet-dialog-portal [data-wallet-ui]')
// The portal wraps content in a div with data-wallet-theme attribute
const portalElement = page.locator('#wallet-dialog-portal [data-wallet-theme]')

const bgColor = await portalElement.evaluate((el) => {
return getComputedStyle(el).getPropertyValue('--wui-color-bg').trim()
Expand Down
4 changes: 2 additions & 2 deletions e2e/tests/theme/dark-mode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { test, expect, DARK_THEME_COLORS } from '../../fixtures/theme'
test.describe('Dark Mode Theme', () => {
test.beforeEach(async ({ page, selectTheme, waitForThemeStable }) => {
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
await selectTheme('dark')
await waitForThemeStable()
})
Expand All @@ -27,7 +27,7 @@ test.describe('Dark Mode Theme', () => {

test('should set data-theme attribute to dark', async ({ page }) => {
const dataTheme = await page
.locator('[data-wallet-ui]')
.locator('[data-wallet-theme]')
.getAttribute('data-theme')
expect(dataTheme).toBe('dark')
})
Expand Down
4 changes: 2 additions & 2 deletions e2e/tests/theme/light-mode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { test, expect, LIGHT_THEME_COLORS } from '../../fixtures/theme'
test.describe('Light Mode Theme', () => {
test.beforeEach(async ({ page, selectTheme, waitForThemeStable }) => {
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
await selectTheme('light')
await waitForThemeStable()
})
Expand All @@ -27,7 +27,7 @@ test.describe('Light Mode Theme', () => {

test('should set data-theme attribute to light', async ({ page }) => {
const dataTheme = await page
.locator('[data-wallet-ui]')
.locator('[data-wallet-theme]')
.getAttribute('data-theme')
expect(dataTheme).toBe('light')
})
Expand Down
10 changes: 5 additions & 5 deletions e2e/tests/theme/system-preference.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ test.describe('System Theme Preference', () => {
}) => {
await page.emulateMedia({ colorScheme: 'light' })
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
await selectTheme('system')

await assertLightTheme()
Expand All @@ -21,7 +21,7 @@ test.describe('System Theme Preference', () => {
}) => {
await page.emulateMedia({ colorScheme: 'dark' })
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
await selectTheme('system')

await assertDarkTheme()
Expand All @@ -32,11 +32,11 @@ test.describe('System Theme Preference', () => {
selectTheme,
}) => {
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
await selectTheme('system')

const dataTheme = await page
.locator('[data-wallet-ui]')
.locator('[data-wallet-theme]')
.getAttribute('data-theme')
expect(dataTheme).toBeNull()
})
Expand All @@ -48,7 +48,7 @@ test.describe('System Theme Preference', () => {
assertDarkTheme,
}) => {
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
await selectTheme('system')

// Start with light
Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/theme/theme-switching.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { test, expect } from '../../fixtures/theme'
test.describe('Theme Switching', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
})

test('should switch from light to dark', async ({
Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/visual/dark-mode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { test, expect } from '@playwright/test'
test.describe('Visual Regression - Dark Mode', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
await page.locator('select').selectOption('dark')
// Wait for fonts and images to load
await page.waitForLoadState('networkidle')
Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/visual/light-mode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { test, expect } from '@playwright/test'
test.describe('Visual Regression - Light Mode', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForSelector('[data-wallet-ui]')
await page.waitForSelector('[data-wallet-theme]')
await page.locator('select').selectOption('light')
// Wait for fonts and images to load
await page.waitForLoadState('networkidle')
Expand Down
10 changes: 6 additions & 4 deletions packages/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Theme colors are CSS custom properties that can be overridden globally or per-in
**Global override (in your CSS):**

```css
[data-wallet-ui] {
[data-wallet-theme] {
--wui-color-primary: #8b5cf6;
--wui-color-primary-hover: #7c3aed;
--wui-color-primary-text: #ffffff;
Expand Down Expand Up @@ -516,7 +516,7 @@ v1.0 introduces a redesigned CSS architecture. Here's what changed:
### Breaking Changes

1. **CSS Variable Prefix**: Variables now use `--wui-` prefix (previously undocumented)
2. **Theme Variables Location**: Defined on `[data-wallet-ui]` element
2. **Theme Variables Location**: Defined on `[data-wallet-theme]` element (for CSS variable inheritance)
3. **Button Data Attribute**: Buttons now have `data-wallet-button` attribute for targeting

### New Features
Expand All @@ -527,7 +527,7 @@ v1.0 introduces a redesigned CSS architecture. Here's what changed:

3. **CSS Variable Overrides**: Override any theme color via CSS:
```css
[data-wallet-ui] {
[data-wallet-theme] {
--wui-color-primary: #your-color;
}
```
Expand All @@ -543,7 +543,9 @@ v1.0 introduces a redesigned CSS architecture. Here's what changed:

### Migration Steps

1. Update any custom CSS targeting the library to use the new `[data-wallet-ui]` selector and `--wui-*` variables
1. Update any custom CSS targeting the library:
- Use `[data-wallet-theme]` for CSS variable overrides
- Use `[data-wallet-button]` for targeting button elements

2. If you were using workarounds for customization, you can likely simplify using the new APIs

Expand Down
27 changes: 23 additions & 4 deletions packages/react/scripts/post-process-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@
* Post-processes the generated Tailwind CSS file to ensure utility classes
* are scoped to wallet UI components.
*
* This script adds [data-wallet-ui] prefix to CSS class selectors to scope
* them to wallet UI components. This ensures styles only apply within the
* WalletUIProvider wrapper and don't leak to the consumer's application.
* This script adds [data-wallet-ui] scoping to CSS class selectors. It generates
* two selector patterns for each utility class:
*
* 1. [data-wallet-ui].class - for elements that have the attribute directly (buttons)
* 2. [data-wallet-ui] .class - for descendants of elements with the attribute (portal content)
*
* This approach allows:
* - Buttons with data-wallet-ui to receive utility classes directly
* - Portal wrappers with data-wallet-ui to scope utility classes to their children
* - No utility class leakage to consumer app elements
*
* With CSS @layer, consumer CSS (unlayered) automatically beats the layered
* library CSS, enabling easy customization without !important.
Expand All @@ -22,6 +29,7 @@ function isTopLevelClassSelector(line) {
// Skip existing prefixed lines, theme selectors, at-rules, and comments
if (
line.includes('[data-wallet-ui]') ||
line.includes('[data-wallet-theme]') ||
trimmed.startsWith('@') ||
trimmed.startsWith('/*') ||
trimmed.startsWith('*')
Expand All @@ -41,7 +49,18 @@ async function main() {
// Process each line
const processedLines = lines.map((line) => {
if (isTopLevelClassSelector(line)) {
// Replace the first occurrence of a period with '[data-wallet-ui] .'
// Extract the leading whitespace and the selector
const match = line.match(/^(\s*)(\.[^\{]+)\{(.*)$/)
if (match) {
const [, indent, selector, rest] = match
// Trim the selector to remove trailing spaces before the opening brace
const trimmedSelector = selector.trim()
// Generate both patterns:
// 1. [data-wallet-ui].class - element has attribute directly
// 2. [data-wallet-ui] .class - element is descendant
return `${indent}[data-wallet-ui]${trimmedSelector}, [data-wallet-ui] ${trimmedSelector} {${rest}`
}
// Fallback to original behavior if regex doesn't match
return line.replace(/(\s*)\./, '$1[data-wallet-ui] .')
}
return line
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/ConnectWalletButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const ConnectWalletButton = React.forwardRef<
return (
<button
ref={ref}
data-wallet-ui
data-wallet-button
className={cn(baseStyles, sizeClasses[size], className)}
style={style}
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/ConnectWalletMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export function ConnectWalletMenu({ children }: ConnectWalletMenuProps) {
{trigger}
<FloatingPortal id="wallet-dialog-portal">
{isOpen && (
<div data-wallet-ui data-theme={dataTheme}>
<div data-wallet-theme data-wallet-ui data-theme={dataTheme}>
<FloatingOverlay
className="grid place-items-center px-4 z-50 transition-opacity duration-150 ease-in-out bg-[var(--wui-color-overlay)] data-[state=starting]:opacity-0 data-[state=exiting]:opacity-0 data-[state=entered]:opacity-100"
data-state={animationState}
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/ConnectedWalletButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const ConnectedWalletButton = React.forwardRef<
return (
<button
ref={ref}
data-wallet-ui
data-wallet-button
className={cn(baseStyles, sizeClasses[size], className)}
style={style}
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/ConnectedWalletMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ function ConnectedWalletMenuContent({ children }: ConnectedWalletMenuProps) {
{isOpen && (
<FloatingPortal>
<FloatingFocusManager context={context} modal={false}>
<div data-wallet-ui data-theme={dataTheme}>
<div data-wallet-theme data-wallet-ui data-theme={dataTheme}>
<div
ref={refs.setFloating}
style={floatingStyles}
Expand Down
27 changes: 15 additions & 12 deletions packages/react/src/input.css
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
/*
CSS Architecture for use-wallet-ui

All styles are scoped to [data-wallet-ui] to:
1. Prevent library styles from leaking into consumer apps
2. Provide enough specificity to override consumer CSS resets (e.g., * { margin: 0 })
3. Enable easy customization via [data-wallet-ui] selector overrides
Two scoping attributes are used:
- [data-wallet-theme]: Provider-level wrapper for CSS variable definitions
- [data-wallet-ui]: Component-level attribute for utility class scoping and CSS resets

This separation allows:
1. CSS variables to inherit from provider to all descendants
2. Consumers to override variables via ancestor selectors (same specificity, load order wins)
3. Utility classes to only affect library components, not consumer app elements

Note: We use a minimal reset that only targets library component elements,
NOT Tailwind's full preflight, to avoid affecting consumer app elements
that may be inside the WalletUIProvider wrapper.
NOT Tailwind's full preflight, to avoid affecting consumer app elements.
*/

@import 'tailwindcss/theme.css';
Expand All @@ -21,7 +24,7 @@
Note: We use [data-wallet-button] to target only the library's buttons,
not custom trigger buttons that consumers may pass as children.
*/
[data-wallet-ui] [data-wallet-button],
[data-wallet-ui][data-wallet-button],
[data-wallet-ui] [role="dialog"],
[data-wallet-ui] [role="menu"],
[data-wallet-ui] [role="listbox"] {
Expand All @@ -35,7 +38,7 @@

/* Button-specific reset - for library buttons and buttons inside dialogs/menus */
/* Uses :where() to reduce specificity, allowing utility classes to override */
[data-wallet-ui] :where([data-wallet-button]),
[data-wallet-ui]:where([data-wallet-button]),
[data-wallet-ui] :where([role="dialog"]) button,
[data-wallet-ui] :where([role="menu"]) button,
[data-wallet-ui] :where([role="listbox"]) button {
Expand Down Expand Up @@ -70,7 +73,7 @@
/* Theme - CSS custom properties */

/* Light mode (default) */
[data-wallet-ui] {
[data-wallet-theme] {
--wui-color-primary: #2d2df1;
--wui-color-primary-hover: #2929d9;
--wui-color-primary-text: #ffffff;
Expand All @@ -93,7 +96,7 @@
}

/* Dark mode via data-theme attribute (explicit) */
[data-wallet-ui][data-theme='dark'] {
[data-wallet-theme][data-theme='dark'] {
--wui-color-primary: #bfbff9;
--wui-color-primary-hover: #d4d4fa;
--wui-color-primary-text: #001324;
Expand All @@ -116,7 +119,7 @@
}

/* Dark mode via .dark class on ancestor (Tailwind convention) */
.dark [data-wallet-ui]:not([data-theme='light']) {
.dark [data-wallet-theme]:not([data-theme='light']) {
--wui-color-primary: #bfbff9;
--wui-color-primary-hover: #d4d4fa;
--wui-color-primary-text: #001324;
Expand All @@ -140,7 +143,7 @@

/* Dark mode via system preference (when theme="system" or no explicit theme) */
@media (prefers-color-scheme: dark) {
[data-wallet-ui]:not([data-theme='light']):not([data-theme='dark']) {
[data-wallet-theme]:not([data-theme='light']):not([data-theme='dark']) {
--wui-color-primary: #bfbff9;
--wui-color-primary-hover: #d4d4fa;
--wui-color-primary-text: #001324;
Expand Down
Loading
Loading