DEV Community

ZeeshanAli-0704
ZeeshanAli-0704

Posted on

Modern Web UI

Modern Web UI: A practical “what, why, when, how” guide with code

This article collects proven practices for building fast, accessible, maintainable apps. For each topic: what it is, why it matters, when to use it, and how to ship it—plus short code snippets.

Part A — Performance, theming, and delivery

1) Atomic/colocated styles (tiny CSS, safe overrides)

  • What: Generate small, atomic CSS classes and colocate styles with components.
  • Why: Reduces CSS size and avoids cascade/specificity issues. Styles ship and get deleted with components.
  • When: Componentized apps at scale; frequent refactors; design system tokens.
  • How: Use a compiler/runtime to split style objects into deduped rules; compose in render order.
  • Example:
import { stylex } from 'stylex';
const styles = stylex.create({
  text: { fontSize: '16px', fontWeight: 'normal' },
  emphasis: { fontWeight: 'bold' },
});
export function MyComponent({ emphasized = false }) {
  return <span className={stylex(styles.text, emphasized && styles.emphasis)}>Hello</span>;
}
Enter fullscreen mode Exit fullscreen mode

2) px→rem conversion (accessible text scaling)

  • What: Convert px to rem at build time.
  • Why: Respects user/system font scaling without breaking layout.
  • When: Global typography and spacing; responsive design.
  • How: PostCSS pxtorem; set html font-size to 100%.
  • Example:
// postcss.config.js
module.exports = { plugins: { 'postcss-pxtorem': { rootValue: 16, propList: ['*'] } } };
Enter fullscreen mode Exit fullscreen mode
html { font-size: 100%; }
Enter fullscreen mode Exit fullscreen mode

3) CSS variables for theming (e.g., dark mode)

  • What: Tokenize with CSS variables; toggle via a root class/attribute.
  • Why: Instant theme switches with no extra requests or specificity hacks.
  • When: Dark mode, brand themes, high-contrast modes.
  • How: Declare tokens in :root; flip values with .dark (or data-theme).
  • Example:
:root { --bg:#fff; --fg:#111; --card-radius:8px; }
.dark { --bg:#111; --fg:#eee; }
.card { background:var(--bg); color:var(--fg); border-radius:var(--card-radius); }
Enter fullscreen mode Exit fullscreen mode
export function setDark(on) { document.documentElement.classList.toggle('dark', !!on); }
Enter fullscreen mode Exit fullscreen mode

4) Inline SVG icons (no flicker, easy theming)

  • What: Bundle SVGs and control fill/stroke via CSS variables/currentColor.
  • Why: No network hops/FOUC; theme with tokens; crisp rendering.
  • When: Icon systems, dynamic colors, offline-friendly UIs.
  • How: Export React SVG components; set fill to var(--icon, currentColor).
  • Example:
export function CheckIcon(props: React.SVGProps<SVGSVGElement>) {
  return (
    <svg width="16" height="16" fill="var(--icon, currentColor)" aria-hidden {...props}>
      <path d="M6 10L3.6 7.6 5 6.2 6 7.2 10.6 2.6 12 4z" />
    </svg>
  );
}
Enter fullscreen mode Exit fullscreen mode

5) JavaScript “tiers” (faster paint and interactivity)

  • What: Load code in priority waves (shell, above-the-fold, later work).
  • Why: Better time-to-paint and time-to-interactive.
  • When: Large apps; route-based and widget-based splitting.
  • How: React.lazy/Suspense for visible code; idle callbacks for non-critical work.
  • Example:
export function AppShell() { return <header>App</header>; }
const Hero = React.lazy(() => import(/* webpackPrefetch: true */ './Hero'));
export function Home() {
  return (
    <>
      <AppShell />
      <React.Suspense fallback={<div className="skeleton hero" />}>
        <Hero />
      </React.Suspense>
    </>
  );
}
requestAnimationFrame(() => {
  requestAnimationFrame(() => {
    (window.requestIdleCallback || ((cb) => setTimeout(cb, 1)))(async () => {
      const analytics = await import('./analytics'); analytics.init();
    }, { timeout: 2000 } as any);
  });
});
Enter fullscreen mode Exit fullscreen mode

6) Experiment-driven code splitting (only download your variant)

  • What: Load just the A/B variant assigned to the user.
  • Why: Cuts bytes and CPU; avoids shipping both versions.
  • When: Feature flags, experiments, staged rollouts.
  • How: Set variant early (server/meta); lazy-load only that chunk.
  • Example:
const expNew = document.querySelector('meta[name="exp-New"]')?.content === '1';
const loadVariant = expNew
  ? () => import(/* webpackChunkName:"composer-new" */ './ComposerNew')
  : () => import(/* webpackChunkName:"composer-old" */ './ComposerOld');
export const Composer = React.lazy(loadVariant);
Enter fullscreen mode Exit fullscreen mode

7) Data-driven module loading (renderers by data type)

  • What: Load components based on data discriminators (e.g., __typename).
  • Why: Only fetch code you need for the current data shape.
  • When: Heterogeneous feeds; plugin-like UIs; GraphQL polymorphism.
  • How: Switch on type and lazy-load; use Relay @module/@match if available.
  • Example (Apollo-like):
export function Post({ post }) {
  switch (post.__typename) {
    case 'PhotoPost': {
      const Photo = React.lazy(() => import('./PhotoComponent'));
      return <React.Suspense fallback={<div className="skeleton photo" />}><Photo data={post} /></React.Suspense>;
    }
    case 'VideoPost': {
      const Video = React.lazy(() => import('./VideoComponent'));
      return <React.Suspense fallback={<div className="skeleton video" />}><Video data={post} /></React.Suspense>;
    }
    default: return null;
  }
}
Enter fullscreen mode Exit fullscreen mode

8) JavaScript bundle budgets (prevent size creep)

  • What: Enforce size limits per bundle/route/tier in CI.
  • Why: Prevents regressions and maintains performance SLOs.
  • When: Always in CI; especially for critical routes.
  • How: Use size-limit (or build tooling) for gzip/brotli targets.
  • Example:
// .size-limit.js
module.exports = [
  { path: 'dist/tier1-*.js', limit: '60 KB' },
  { path: 'dist/tier2-*.js', limit: '160 KB' },
  { path: 'dist/tier3-*.js', limit: '320 KB' },
];
Enter fullscreen mode Exit fullscreen mode

9) Preload on server; GraphQL @stream and @defer

  • What: Start data early; stream lists and defer slow parts for sooner UI.
  • Why: Fewer waterfalls; show useful content faster.
  • When: SSR/Streaming SSR; long lists; mixed-speed subtrees.
  • How: React 18 streaming SSR + Relay; wrap deferred/streamed sections in Suspense.
  • Example (server):
import { renderToPipeableStream } from 'react-dom/server';
app.get('*', (req, res) => {
  const { pipe } = renderToPipeableStream(<App url={req.url} />, {
    bootstrapScripts: ['/client.js'],
    onShellReady() { res.setHeader('Content-Type', 'text/html'); pipe(res); },
  });
});
Enter fullscreen mode Exit fullscreen mode

10) Route map + predictive prefetch (parallel code+data)

  • What: Manifest-driven loader; prefetch on hover/focus/mousedown.
  • Why: Makes navigation feel instant; parallelizes code and data.
  • When: High-traffic nav paths; authenticated apps with predictable flows.
  • How: Route manifest with loadComponent + getQuery; SmartLink prefetches on intent.
  • Example:
export const routeManifest = {
  '/profile/:id': {
    loadComponent: () => import('./Profile/ProfileRoute'),
    getQuery: (p: { id: string }) => ({ document: ProfileQuery, variables: { id: p.id } }),
  },
} as const;
Enter fullscreen mode Exit fullscreen mode
function prefetchRoute(href: string) {
  const match = matchRoute(routeManifest, href);
  if (!match) return;
  match.def.loadComponent();
  const { document, variables } = match.def.getQuery(parseParams(match, href));
  preloadQuery(relayEnv, document, variables);
}
Enter fullscreen mode Exit fullscreen mode

Part B — Accessibility, quality, and developer experience

1) Guardrails early: linting + static typing for a11y/i18n

  • What: ESLint rules plus typed props to enforce labels, ARIA, and i18n.
  • Why: Prevents common issues before they ship; scales across teams.
  • When: Always—in local dev and CI.
  • How: jsx-a11y + TypeScript/Flow brand types for TranslatedString.
  • Example:
// .eslintrc.js
module.exports = {
  parser: '@typescript-eslint/parser',
  plugins: ['jsx-a11y', '@typescript-eslint', 'react'],
  extends: ['eslint:recommended','plugin:react/recommended','plugin:jsx-a11y/recommended','plugin:@typescript-eslint/recommended'],
  rules: { 'jsx-a11y/alt-text': 'error', 'jsx-a11y/anchor-is-valid': 'error' }
};
Enter fullscreen mode Exit fullscreen mode
type Brand<K, T> = K & { __brand: T };
export type TranslatedString = Brand<string, 'Translated'>;
Enter fullscreen mode Exit fullscreen mode

2) Scalable font sizes: author in px, ship rems

  • What: Build-time px→rem conversion.
  • Why: Honors user font settings; avoids layout breakage.
  • When: Global CSS pipeline.
  • How: PostCSS pxtorem, root font-size 100%.
  • Example:
module.exports = { plugins: { 'postcss-pxtorem': { rootValue: 16, propList: ['*'] } } };
Enter fullscreen mode Exit fullscreen mode

3) Contextual headings (automatic h1–h6 hierarchy)

  • What: Context tracks heading level; components render correct h1–h6.
  • Why: Better screen reader navigation; resilient to layout changes.
  • When: Complex/nested layouts and reusable sections.
  • How: React Context + Section/Heading components.
  • Example:
const HeadingLevelContext = React.createContext(1);
export function Heading({ children }: { children: React.ReactNode }) {
  const level = React.useContext(HeadingLevelContext);
  const Tag = `h${Math.min(level,6)}` as keyof JSX.IntrinsicElements;
  return <Tag>{children}</Tag>;
}
Enter fullscreen mode Exit fullscreen mode

4) Contextual keyboard commands (discoverable, conflict-free)

  • What: Central registry for shortcuts; Shift+? help overlay.
  • Why: Avoids conflicts and mystery handlers; improves discoverability.
  • When: Power-user workflows; editors; dashboards.
  • How: Provider manages registration; hook binds handlers; overlay lists active commands.
  • Example: see “keycommands.tsx” pattern in prompt.

5) Runtime analysis overlay (catch dynamic issues)

  • What: MutationObserver checks DOM for a11y issues and overlays highlights.
  • Why: Finds issues static tools miss; fast feedback in dev.
  • When: Dev and secured staging only.
  • How: Observe DOM, heuristically flag nodes, draw overlays.
  • Example:
export function startA11yRuntimeOverlay() {
  const observer = new MutationObserver((ms) => {
    for (const m of ms) m.addedNodes.forEach((n) => n instanceof Element && n.querySelectorAll('*').forEach(inspect));
  });
  observer.observe(document.documentElement, { childList: true, subtree: true });
  return () => observer.disconnect();
}
Enter fullscreen mode Exit fullscreen mode

6) Accessible base components (semantic-first primitives)

  • What: Unstyled primitives encode proper semantics/ARIA; DS components style them.
  • Why: One correct implementation reused everywhere.
  • When: Design systems; multi-team codebases.
  • How: Prefer native elements; add ARIA only when needed.
  • Example:
export const ButtonBase = React.forwardRef<HTMLButtonElement, { label: string }>(
  ({ label, ...rest }, ref) => <button ref={ref} type="button" aria-label={label} {...rest}>{label}</button>
);
Enter fullscreen mode Exit fullscreen mode

7) Maintaining focus (roving tabindex, lists/grids)

  • What: Parent controls active index; only active item gets tabIndex=0.
  • Why: Predictable keyboard navigation; handles wrap and large lists.
  • When: Menus, lists, grids, dropdowns.
  • How: Context to track active item; keyboard handlers move focus.
  • Example: see “FocusList.tsx” pattern in prompt.

8) Screen reader announcements (aria-live alerts)

  • What: Non-intrusive status updates without moving focus.
  • Why: Confirms actions; keeps keyboard focus intact.
  • When: Form submissions, async results, background updates.
  • How: Hidden region with role=status/alert; update textContent.
  • Example:
const alert = useAccessibilityAlert();
alert('Your comment has been submitted');
Enter fullscreen mode Exit fullscreen mode

9) Ongoing monitoring and regression prevention

  • What: CI + synthetic tests + optional runtime sampling.
  • Why: Prevent regressions and keep a11y observable.
  • When: Always in CI; targeted e2e on critical flows.
  • How: Playwright + axe-core; fail builds on serious violations.
  • Example:
const results = await new AxeBuilder({ page }).withTags(['wcag2a','wcag2aa']).analyze();
expect(results.violations.filter(v => ['critical','serious'].includes(v.impact!))).toEqual([]);
Enter fullscreen mode Exit fullscreen mode

10) Putting it together (build it in, don’t bolt it on)

  • What: A cohesive approach across lint/types, base components, focus utilities, shortcuts, and runtime checks.
  • Why: Fewer defects, faster delivery, better UX for all.
  • When: From the start; bake into templates and scaffolding.
  • How: Document patterns, provide codemods, and enforce via CI.

Security, privacy, and compliance reminders

  • Prefetching, streaming, and experiments can fetch or expose user-specific data before explicit action. Ensure alignment with your organization’s security, privacy, consent, and data minimization policies. Apply appropriate Cache-Control, CSP, and data scoping.
  • Validate third-party libraries and build tools (e.g., CSS-in-JS compilers, PostCSS plugins, size-limit, eslint-plugin-jsx-a11y, axe-core, Playwright);

  • Limit diagnostics to development or secured staging. Do not expose runtime overlays to end users. Minimize data in logs; scrub user content; set retention appropriately.

Quick “when to reach for what”

  • Faster first paint/TTI: JS tiers, route prefetch, server preloads/streaming, bundle budgets.
  • Smaller CSS and safer overrides: atomic/colocated styles, CSS variables.
  • Theming and icons: CSS variables, inline SVG with tokens/currentColor.
  • Accessibility at scale: a11y linting + typed labels, contextual headings, base components, focus utilities, aria-live alerts, runtime overlay in dev.
  • Only ship what’s needed: experiment-driven and data-driven code splitting.

Use this guide as a checklist: start with guardrails (lint/types), set up bundle budgets, adopt rem-based scaling and tokens, then layer in code/data prefetching and streaming where it meaningfully improves user experience.

  • Credits: meta engineering blog

Top comments (0)