Introduction

In modern React development, maintaining consistency across your codebase is crucial for scalability, maintainability, and an improved developer experience (DX). A powerful approach to achieving this is creating a single source of truth for base components. This article explores the benefits of centralizing base HTML elements and guides you through building a foundational Html component in TypeScript.

By the end, you will be able to use the new component as follows:

<Html.button
  className={["btn", true && "btn-primary"]}
  style={{ padding: "10px" }}
>
  Click Me!
</Html.button>

<Html.a
  href="https://example.com"
  title="Example Site"
  className={["link", { "link-active": isActive }]}
  style={{ color: "blue" }}
>
  Visit Example
</Html.a>

Why a Single Source of Truth Matters

Centralized Bug Fixes & Enhancements

  • One Fix, Many Benefits: Updating the centralized logic for class names or props applies changes globally.
  • Consistent Behavior: Derived components (e.g., <Html.button>, <Html.a>) behave uniformly, reducing inconsistencies.

Improved Developer Experience (DX)

  • Extended Props: Enhancing native HTML elements (e.g., adding conditional class names) avoids repetitive code.
  • Unified API: A consistent interface simplifies onboarding and usage.

Scalability & Maintainability

  • Centralized Logic: Easier debugging, scaling, and updates.
  • Reduced Boilerplate: Reusable logic minimizes code duplication.

Overview of Our Foundational Html Component

Our goal is to create a centralized Html object that:

  • Inherits all native properties.
  • Supports built-in class name merging.
  • Is written in TypeScript with ref forwarding for type safety.

This approach promotes DRY (Don’t Repeat Yourself) principles, ensuring consistency and future-proofing enhancements.


Tutorial: Building the Component

Step 1: Setting Up Your Project

Initialize a new React project with TypeScript:

npx create-react-app my-app --template typescript
cd my-app
npm start

Step 2: Creating the Component Generator

Create factory.tsx:

import React from "react";

function classNames(...args: any[]): string {
  const classes: string[] = [];

  args.forEach((arg) => {
    if (!arg) return;
    if (typeof arg === "string" || typeof arg === "number") {
      classes.push(String(arg));
    } else if (Array.isArray(arg)) {
      const inner = classNames(...arg);
      if (inner) classes.push(inner);
    } else if (typeof arg === "object") {
      for (const key in arg) {
        if (arg[key]) classes.push(key);
      }
    }
  });

  return classes.join(" ");
}

export function factory<Tag extends keyof JSX.IntrinsicElements>(tag: Tag) {
  const Component = React.forwardRef<HTMLElement, JSX.IntrinsicElements[Tag]>(
    (props, ref) => {
      const { className, ...rest } = props;

      return React.createElement(tag, {
        ...rest,
        ref,
        className: classNames(
          ...(Array.isArray(className) ? className : [className])
        ),
      });
    }
  );

  Component.displayName = `Html.${tag}`;

  return Component;
}

Step 3: Building the Html Collection

Create html.tsx:

import React from "react";
import { factory } from "./factory";

const tags = ["a", "button", "div", "span", "p", "h1", "h2"] as const;

type HtmlComponents = {
  [K in (typeof tags)[number]]: ReturnType<typeof createHtmlComponent>;
};

const Html = {} as HtmlComponents;

tags.forEach((tag) => {
  Html[tag] = factory(tag);
});

export default Html;

Step 4: Using the Html Component

Modify App.tsx to use the new components:

import React, { useState } from "react";
import Html from "./components/html";
import "./styles.css";

export default function App() {
  const [isPrimary, setIsPrimary] = useState(true);
  const [isActive, setIsActive] = useState(false);

  return (
    <div style={{ padding: "20px" }}>
      <Html.button
        className={["btn", isPrimary && "btn-primary"]}
        onClick={() => setIsPrimary((prev) => !prev)}
      >
        Toggle Primary Button
      </Html.button>

      <Html.a
        href="https://overctrl.com"
        title="Example Site"
        className="link"
        style={{ display: "block", marginTop: "20px", color: "blue" }}
        onClick={() => setIsActive((prev) => !prev)}
      >
        Visit overctrl
      </Html.a>
    </div>
  );
}

export default App;

Demo


The Benefits of the HTML Component Approach

Adopting a single source of truth for your base HTML components unlocks several key advantages, making your codebase more consistent, maintainable, and scalable. Let's explore the primary benefits of this approach.

1. Consistent Behavior Across Your Application

By standardizing components like <Html.button> or <Html.a>, you ensure predictable behavior throughout your codebase. Any enhancements or bug fixes applied at the base level propagate seamlessly, eliminating inconsistencies in how class names are merged or props are managed.

2. Simplified Bug Fixes and Feature Enhancements

With a centralized component structure, debugging and feature updates become significantly more efficient. For instance, if an issue arises with how conditional class names are processed, updating the mergeClassNames function applies the fix universally—saving time and effort.

3. Enhanced Developer Experience

A well-defined API reduces cognitive load for developers, ensuring they interact with a familiar, intuitive component structure. This consistency minimizes errors and accelerates development, while extensions such as theming and accessibility can be introduced effortlessly at a single touchpoint.

4. Improved Maintainability as Your Project Scales

As your application grows, managing a scattered and redundant component logic can become cumbersome. By centralizing logic within the Html component, you maintain a cleaner and more structured codebase, reducing duplication and streamlining future updates.


Expanding Your HTML Component

Once you have established a foundational Html component, there are numerous ways to extend its capabilities. Here are some powerful enhancements:

1. Theming and Styling Enhancements

  • Global Theme Support: Integrate with styling libraries like styled-components or Emotion to apply global themes effortlessly.
  • Responsive Design: Introduce responsive properties that allow developers to define styles dynamically based on breakpoints.

2. Accessibility Improvements

  • Built-in ARIA Support: Automatically inject relevant ARIA attributes, ensuring accessibility compliance.
  • Focus Management: Implement better keyboard navigation and focus state handling for interactive elements like buttons and links.

3. Animation and Transitions

  • CSS Animations: Leverage animation libraries such as Framer Motion to provide seamless state transitions.
  • State Transitions: Introduce hooks for managing UI state transitions with minimal boilerplate.

4. Advanced Prop Validation and Warnings

  • Runtime Checks: Issue warnings for deprecated or conflicting props during development.
  • TypeScript Enhancements: Utilize stricter TypeScript interfaces for better compile-time validation.

5. Integration with Design Systems

  • Design Tokens: Map design tokens (colors, spacing, typography) to CSS variables for consistency.
  • Component Variants: Use variant props (primary, secondary, danger) to streamline styling and avoid redundant code.

6. Extending Beyond Standard HTML Elements

  • Custom Elements: Apply the same structured approach to non-HTML elements like SVG icons or complex UI components.
  • Higher-Order Components (HOCs): Wrap Html components with additional logic for logging, error boundaries, or performance monitoring.

Each of these enhancements further solidifies the value of using a single source of truth, ensuring your component system remains flexible and future-proof.



Conclusion

Standardizing base components within a React application is a powerful strategy for improving maintainability, scalability, and consistency. By implementing a foundational Html component, you achieve:

  • A centralized structure for className management and prop handling.
  • A more intuitive developer experience with predictable component behavior.
  • Simplified bug fixes and feature upgrades with minimal refactoring.

This approach reduces redundancy and sets the stage for future enhancements—whether through global theming, accessibility improvements, or animation integration.

We encourage you to experiment with this methodology in your projects and explore the extension ideas outlined above. Adopting a single source of truth will lead to a more efficient, maintainable, and enjoyable development experience.

Happy coding!