UI Kit

Customize UI

Theme sufwan-local-test UI components with CSS variables, next/font, labels, formatting, and component slots

Customize sufwan-local-test UI

sufwan-local-test UI is designed to be embedded in partner apps without forking components. The supported customization surface is:

  • CSS variables on the sufwan-local-test root for brand, typography, radius, shadow, and z-index tokens
  • AggProvider config for theme mode, labels, formatting, feature flags, search wiring, and wallet actions
  • component className / classNames props for layout composition

Import @agg-build/ui/styles.css once, then load your app CSS after it. sufwan-local-test defaults are low specificity, so :root overrides can work, and .agg-root overrides win because they are closer to the sufwan-local-test UI tree.

Minimal integration

app/providers.tsx
import "@agg-build/ui/styles.css";
import "./agg-theme.css";

import { QueryClient, QueryClientProvider } from "@agg-build/hooks";
import { createAggClient } from "@agg-build/sdk";
import { AggProvider } from "@agg-build/ui";
import type { ReactNode } from "react";

const client = createAggClient({
  baseUrl: "https://api.t.sufwankhalid.com",
  appId: "your-app-id",
  wsUrl: "wss://ws.t.sufwankhalid.com/ws",
});

const queryClient = new QueryClient();

export function Providers({ children }: { children: ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      <AggProvider
        client={client}
        config={{
          general: {
            locale: "en-US",
            theme: "light",
            rootClassName: "acme-agg-theme",
          },
          features: {
            enableAnimations: true,
            enableLiveUpdates: true,
            enableGradients: false,
            showFeesBreakdown: true,
          },
        }}
      >
        {children}
      </AggProvider>
    </QueryClientProvider>
  );
}
app/page.tsx
import { HomePage } from "@agg-build/ui/pages";

export default function Page() {
  return <HomePage withHeader />;
}

AggProvider renders an .agg-root wrapper around its children. If you pass general.rootClassName, that class is added to the same root so you can scope partner-specific branding without affecting other sufwan-local-test embeds on the page.

Import order

Load sufwan-local-test styles first and partner overrides second:

import "@agg-build/ui/styles.css";
import "./agg-theme.css";

Then put variables in your CSS:

agg-theme.css
.agg-root.acme-agg-theme {
  --agg-color-primary: #2563eb;
  --agg-color-primary-hover: #1d4ed8;
  --agg-color-secondary: #ffffff;
  --agg-color-secondary-hover: #f1f5f9;
  --agg-color-border: #cbd5e1;
  --agg-color-foreground: #0f172a;
  --agg-color-muted-foreground: #475569;
  --agg-color-on-primary: #ffffff;
  --agg-radius-md: 0.625rem;
  --agg-radius-full: 0.75rem;
  --agg-shadow-card: 0 8px 20px rgb(15 23 42 / 0.08);
}

Custom fonts

Set the sufwan-local-test font token on .agg-root. This affects sufwan-local-test typography utilities, primitives, pages, modals, and portaled surfaces.

Plain CSS

.agg-root {
  --agg-font-family-sans: "Your Font", ui-sans-serif, system-ui, sans-serif;
}

You can also set the token on :root; a value on .agg-root wins if both exist.

Next.js next/font

Expose the generated font variable on an ancestor, then map it into the sufwan-local-test token.

app/layout.tsx
import { Inter } from "next/font/google";
import { Providers } from "./providers";
import "./globals.css";

const inter = Inter({
  subsets: ["latin"],
  variable: "--font-inter",
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={inter.variable}>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
globals.css
.agg-root {
  --agg-font-family-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
}

Light and dark themes

general.theme controls the class and data attributes applied to .agg-root. If you ship custom dark tokens, match sufwan-local-test's dark selector shape so your values win in dark mode:

.dark .agg-root.acme-agg-theme:not(.light),
.agg-dark .agg-root.acme-agg-theme:not(.light),
.agg-root.acme-agg-theme.dark,
.agg-root.acme-agg-theme[data-agg-theme="dark"] {
  --agg-color-primary: #8fb4ff;
  --agg-color-primary-hover: #b4ccff;
  --agg-color-secondary: #0b1220;
  --agg-color-secondary-hover: #172036;
  --agg-color-border: #334155;
  --agg-color-foreground: #e2e8f0;
  --agg-color-muted-foreground: #94a3b8;
  --agg-color-on-primary: #121214;
  --agg-shadow-card: 0 10px 28px rgb(2 6 23 / 0.45);
}

HomePage discovery URL persistence

features.enableEventDiscoveryUrlPersistence enables browser-history persistence for the primary HomePage section's category, subcategory path, venue, and sort filters. Defaults are omitted from the URL, unrelated query parameters are preserved, and the flag is disabled by default.

<AggProvider
  client={client}
  config={{ features: { enableEventDiscoveryUrlPersistence: true } }}
>
  <HomePage />
</AggProvider>

Gradients

Keep semantic colors and gradient definitions separate. features.enableGradients controls whether branded surfaces resolve to flat primary colors or gradient tokens.

<AggProvider
  client={client}
  config={{
    features: {
      enableGradients: true,
    },
  }}
>
  <HomePage />
</AggProvider>
.agg-root.acme-agg-theme {
  --agg-gradient-brand: linear-gradient(90deg, #2563eb, #4f46e5);
  --agg-gradient-brand-hover: linear-gradient(90deg, #1d4ed8, #4338ca);
}

.dark .agg-root.acme-agg-theme:not(.light),
.agg-dark .agg-root.acme-agg-theme:not(.light),
.agg-root.acme-agg-theme.dark,
.agg-root.acme-agg-theme[data-agg-theme="dark"] {
  --agg-gradient-brand: linear-gradient(90deg, #8fb4ff, #c084fc);
  --agg-gradient-brand-hover: linear-gradient(90deg, #b4ccff, #d8b4fe);
}

Public theme tokens

Set these on .agg-root or .agg-root.<your-rootClassName>.

TokenUsage
--agg-color-primaryPrimary CTA color and emphasis states
--agg-color-primary-hoverPrimary action hover color
--agg-color-secondaryCard, popover, modal, and control surfaces
--agg-color-secondary-hoverNeutral hover and selected surfaces
--agg-color-borderBorders and separators
--agg-color-foregroundPrimary text and icon foreground
--agg-color-muted-foregroundSecondary text, metadata, and helper labels
--agg-color-on-primaryText and icons on branded filled surfaces
--agg-color-successPositive status and bid-side color
--agg-color-warningWarning status
--agg-color-errorError status and ask-side color
--agg-color-overlayModal and drawer overlay color
--agg-gradient-brandBranded gradient fill
--agg-gradient-brand-hoverBranded gradient hover fill
--agg-font-family-sansBase sufwan-local-test UI font family
--agg-font-size-2xs through --agg-font-size-4xlTypography size scale
--agg-line-height-3, --agg-line-height-14, --agg-line-height-4 through --agg-line-height-10, --agg-line-height-baseTypography line-height scale
--agg-font-weight-normal, --agg-font-weight-medium, --agg-font-weight-semibold, --agg-font-weight-boldTypography weight scale
--agg-radius-sm, --agg-radius-md, --agg-radius-lg, --agg-radius-xl, --agg-radius-2xl, --agg-radius-fullRadius scale
--agg-shadow-card, --agg-shadow-popover, --agg-shadow-modalElevation tokens
--agg-z-index-dropdown, --agg-z-index-overlay, --agg-z-index-modal, --agg-z-index-toast, --agg-z-index-tooltipDropdown, overlay, modal, toast, and tooltip layering

Avoid overriding bridge tokens such as --color-agg-*, --text-agg-*, and --font-agg-sans directly. They are generated from the public --agg-* tokens.

Copy and labels

Use general.labels when you need to change user-facing text. Label overrides are deep-merged with sufwan-local-test defaults, so you only provide the keys you want to replace.

<AggProvider
  client={client}
  config={{
    general: {
      labels: {
        common: {
          retry: "Try again",
        },
        auth: {
          signIn: "Sign in to Acme",
          deposit: "Add funds",
        },
        eventList: {
          emptyTitle: "No markets match your filters",
        },
      },
    },
  }}
>
  <HomePage />
</AggProvider>

Use labels for product copy. Use CSS variables for visual styling. Use component props for data, callbacks, and layout.

Formatting

Override formatting functions when your app needs a different locale, currency presentation, or date style.

const usdCompact = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  notation: "compact",
  maximumFractionDigits: 1,
});

<AggProvider
  client={client}
  config={{
    general: {
      locale: "en-US",
    },
    formatting: {
      formatCompactCurrency: (value) => usdCompact.format(value),
    },
  }}
>
  <HomePage />
</AggProvider>;

Odds display

formatPercent and formatPrice are two renderings of the same number — both receive a probability between 0 and 1. Discovery surfaces (event tiles, search results, charts) use formatPercent; price surfaces (order book, event page outcome chips, place-order, positions) use formatPrice.

To show a single odds convention app-wide, set both to the same function:

// Decimal odds: 0.62 -> "1.61"
const toDecimalOdds = (probability: number) => (1 / probability).toFixed(2);

<AggProvider
  client={client}
  config={{
    formatting: {
      formatPercent: toDecimalOdds,
      formatPrice: toDecimalOdds,
    },
  }}
>
  <HomePage />
</AggProvider>;

formatPrice is optional. Leave it unset to keep the default cents rendering (62.5¢).

Two values deliberately stay on the built-in formatters even when formatPrice is set, because neither is a price: the order book spread (a difference between two prices, always in cents) and position PnL percentages.

Layout and slots

Use className / classNames for layout composition, not deep CSS selectors.

import { HomePage } from "@agg-build/ui/pages";

export function MarketHome() {
  return (
    <HomePage
      withHeader={false}
      classNames={{
        root: "min-h-screen",
        header: "border-b border-slate-200 bg-white/90 backdrop-blur",
        tabs: "mx-auto w-full max-w-6xl px-6",
        sections: "mx-auto w-full max-w-6xl px-6 py-8 gap-8",
      }}
    />
  );
}

Prefer token overrides for colors, fonts, radius, shadows, and other repeated visual decisions. Use slot class names for spacing, containment, sticky behavior, or one-off layout requirements.

Troubleshooting

On this page