Customize UI Kits Without Breaking Accessibility
accessibilitydesign-systemtokenscustomizationsaasai5 min read

Customize UI Kits Without Breaking Accessibility

Admin

UI kits promise a head start—but as soon as you start customizing, it’s easy to:

  • Break color contrast in dark mode,
  • Remove focus outlines “because they’re ugly”,
  • Turn semantic buttons into plain <div>s,
  • Or introduce inconsistent spacing and typography.

The goal isn’t just to “make it look like your brand”—it’s to do it without shipping regressions that hurt accessibility and usability.

This post walks through safe customization patterns using the SaaS Starter Kit and AI UX Kit, both of which are built with WCAG AA in mind.

🎨 Start With Tokens, Not Components

The fastest way to “rebrand” without breaking things is to work at the token level:

  • Colors
  • Spacing
  • Radius
  • Typography

Both kits use CSS custom properties + Tailwind tokens.

// tailwind.config.ts
export default {
  theme: {
    extend: {
      colors: {
        primary: {
          50: "var(--color-primary-50)",
          100: "var(--color-primary-100)",
          600: "var(--color-primary-600)",
        },
        secondary: {
          100: "var(--color-secondary-100)",
          200: "var(--color-secondary-200)",
        },
      },
    },
  },
}

You customize the variables, not every component:

/* theme.css */
:root {
  --color-primary-50: #f5f3ff;
  --color-primary-100: #e0ddff;
  --color-primary-600: #5b21ff; /* your brand color */

  --color-secondary-100: #f4f4f5;
  --color-secondary-200: #e4e4e7;
}

Because the components consume tokens, they inherit your brand automatically—while keeping spacing, hierarchy, and focus styling intact. (Learn more about how design tokens keep your UI cohesive.)

♿ Keep Contrast in Check

Changing colors is the #1 way to accidentally break accessibility.

Safe pattern:

  1. Change tokens in one place.
  2. Check contrast for key pairs:
    • primary-600 on white
    • text on secondary-100 / secondary-200
    • focus rings vs. backgrounds
  3. Test both light and dark themes (if enabled).

Tools like @axe-core/react, browser devtools, or design tools can help, but the kits already start from WCAG-conscious defaults—you’re adjusting from a good baseline.

🔘 Don’t Remove Focus, Restyle It

It’s tempting to remove focus outlines completely. Instead, restyle them to match your brand.

The kits ship components that already handle focus states; you just adjust tokens or utility classes.

import { Button, ButtonLabel } from "@/components/ui/primitives/button"

export function PrimaryAction() {
  return (
    <Button
      action="primary"
      className="focus-visible:ring-2 focus-visible:ring-primary-300 focus-visible:ring-offset-2"
    >
      <ButtonLabel>Save changes</ButtonLabel>
    </Button>
  )
}

If you want global customization, do it in one place:

/* globals.css */
:root {
  --focus-ring-color: rgba(91, 33, 255, 0.35);
}

button:focus-visible,
input:focus-visible,
[role="button"]:focus-visible {
  outline: 2px solid var(--focus-ring-color);
  outline-offset: 2px;
}

This keeps keyboard navigation intact while still feeling on-brand.

🧱 Wrap, Don’t Rewrite Primitives

The fastest way to break accessibility is to copy-paste internal component code and start editing.

Better pattern: wrap the existing primitive.

// app/components/BrandButton.tsx
import { Button, ButtonProps, ButtonLabel } from "@/components/ui/primitives/button"

type BrandButtonProps = ButtonProps & {
  icon?: React.ReactNode
}

export function BrandButton({ children, icon, ...props }: BrandButtonProps) {
  return (
    <Button
      action="primary"
      className="rounded-full px-5 py-2 text-sm shadow-sm"
      {...props}
    >
      {icon && <span className="mr-2">{icon}</span>}
      <ButtonLabel>{children}</ButtonLabel>
    </Button>
  )
}

Now your app uses BrandButton everywhere, but all the underlying:

  • ARIA attributes,
  • focus behavior,
  • disabled state handling

still come from the original primitive.

🧩 Customizing Complex Components (e.g. PromptInput)

Components like PromptInput (AI UX Kit) or MainSidebar (SaaS Starter Kit) are composed from primitives. To customize them safely:

  1. Identify what should be configurable props (labels, placeholder, icons, badges, etc.).
  2. Use Tailwind className overrides, not structural rewrites.

Example: customizing PromptInput styling and copy:

import {
  PromptInput,
  type PromptInputProps,
} from "@/components/ui/composites/prompt-input"

export function BrandedPromptInput(props: PromptInputProps) {
  return (
    <PromptInput
      placeholder="Ask our assistant anything about your workspace..."
      outlined
      className="bg-basic-white border border-primary-100 shadow-sm"
      {...props}
    />
  )
}

You’ve changed the look and feel without touching:

  • Semantic structure,
  • Keyboard handling,
  • File attachment or audio logic.

🚫 Anti-Patterns to Avoid

Here are common customization mistakes that break accessibility:

  • Turning everything into <div>s:

    • Avoid: onClick on plain div without role/keyboard handlers.
    • Instead: use <button>, <a>, or wrap the existing Button/Link components.
  • Removing focus outlines completely:

    • Avoid: outline-none or focus-visible:outline-none with no replacement.
    • Instead: restyle focus outlines via tokens or utilities.
  • Hardcoding colors inside components:

    • Avoid: className="text-[#888]" scattered across files.
    • Instead: use token-driven classes like text-secondary-600.
  • Copy-pasting core components:

    • Avoid: duplicating internal primitives just to tweak spacing.
    • Instead: wrap in your own component and override via className.

✅ Safe Customization Checklist

Before merging major visual changes:

  • Tokens

    • All brand color changes go through design tokens / CSS variables.
    • Contrast is checked for primary text, buttons, and focus rings.
  • Focus

    • Focus styles are visible on all interactive elements.
    • No global outline: none without a replacement.
  • Semantics

    • Interactive elements use <button>, <a>, or components that wrap them.
    • Icons used as buttons include accessible labels or are decorative only.
  • Structure

    • Heavier customizations are done via wrappers, not internal rewrites.
    • Layout changes don’t hide or detach focusable elements.
  • Regression

    • Key flows (auth, settings, chat) are tested with keyboard only.
    • Screen reader smoke test on at least one platform (NVDA/VoiceOver).

🎯 How thefrontkit Supports Safe Customization

Both kits are built with customization in mind:

  • Token-first design: tweak colors, spacing, and radii centrally.
  • Composable primitives: Button, Input, Field, Form, Tag, etc. are designed to be wrapped.
  • AI-specific composites: PromptInput, ResponseViewer, FeedbackModal, etc. expose props for copy, icons, and layout without needing structural rewrites.

This lets you:

  • Stand up a production-quality UI quickly,
  • Make it feel like your brand,
  • While staying within WCAG AA guardrails.

🚀 Next Steps

If you’re planning to adopt a UI kit but worried about “losing control” of your brand:

  • Start by:
    • Updating tokens (colors, spacing, typography),
    • Introducing a small set of wrapped primitives for brand-specific patterns.
  • Avoid diving straight into rewriting core components.

Explore the kits:

  • SaaS Starter Kit — Token-driven SaaS shell you can safely restyle.
  • AI UX Kit — Customizable AI patterns built on accessible primitives.

With the right approach, you get the best of both worlds: your brand, on top of a solid, accessible foundation you don’t have to build from scratch.

Related Posts