Skip to the content.
Agent Skill Available Download this Agent Skill (SKILL.md) to drop into ~/.claude/skills/ or any Agent-Skills-compatible runtime for AI-assisted Atom work.
Download
atom

Atom

Key Insight

Atoms are the fundamental building blocks of your UI system—think of them as LEGO bricks that do one thing exceptionally well. By keeping atoms focused on a single responsibility (displaying text, capturing input, showing an icon), you create highly reusable components that compose effortlessly into complex interfaces while maintaining consistency across your entire application.

Detailed Description

In the context of Atomic Design, atoms represent the smallest functional units of your user interface. Just as chemical atoms combine to form molecules, UI atoms combine to create more complex components. An atom should encapsulate a single piece of functionality or presentation—a button, an input field, a label, or an icon—nothing more.

The power of atoms lies in their simplicity and reusability. When you build an atom, you’re establishing a contract: this component does one thing, accepts specific props, and behaves predictably. This predictability is crucial for the Universal Frontend Architecture approach, where components must work seamlessly across different frameworks and contexts. A well-designed atom can be used hundreds of times throughout your application without modification.

Atoms serve as the foundation for your design system, enforcing visual and functional consistency. When your Button atom defines padding, font size, and hover states, every button across your application inherits these properties automatically. This centralization eliminates the “slightly different button” problem that plagues many codebases and makes design updates as simple as modifying a single file.

From a performance perspective, atoms are inherently optimizable. Because they’re small and focused, they’re easy to memoize, lazy-load, and tree-shake. Framework-specific optimizations like React.memo or Vue’s functional components work best with simple, pure components—exactly what atoms should be.

In the Universal Frontend Architecture, atoms bridge the gap between design and implementation. They translate design tokens (colors, spacing, typography) into interactive components, creating a shared vocabulary between designers and developers. This alignment accelerates development and ensures design fidelity across all user touchpoints.

Code Examples

Basic Example: Button Atom across frameworks

Here’s the same single-purpose Button atom pulled directly from each chota-* template. The shape is intentionally identical across all four: one prop-driven button that swaps to a Loader while isLoading is true, and emits a click otherwise. Compare how each framework expresses the same pattern.

React

// templates/chota-react-redux/src/ui/atoms/Button/Button.component.jsx
import Loader from "../Loader/Loader.component";
import './Button.style.css';

export default function Button(props) {
  const transformedProps = { ...props };
  delete transformedProps.isLoading;
  if (props.isLoading) {
    return (
      <button {...transformedProps} className={`${props.className} loading-button`}>
        <Loader width="2px" size="1.2rem" color="#fff" />
      </button>
    );
  }
  return <button {...transformedProps}>{props.children}</button>;
}

Angular

// templates/chota-angular-ngrx/src/ui/atoms/Button/Button.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
import LoaderComponent from '../Loader/Loader.component';

@Component({
  selector: 'app-button',
  standalone: true,
  imports: [LoaderComponent],
  templateUrl: './Button.component.html',
  styleUrls: ['./Button.style.css'],
})
export default class ButtonComponent {
  @Input() isLoading = false;
  @Input() classes?: string;
  @Input() type = '';

  get computedClasses() {
    return this.isLoading ? `${this.classes} loading-button` : this.classes;
  }

  @Output() onClick = new EventEmitter<Event>();
}
<!-- Button.component.html -->
@if (isLoading) {
  <button [class]="computedClasses" [disabled]="isLoading">
    <app-loader width="2px" size="1.2rem" color="#fff"></app-loader>
  </button>
} @else {
  <button [type]="type || 'button'" (click)="onClick.emit($event)" [class]="computedClasses">
    <ng-content></ng-content>
  </button>
}

Vue

<!-- templates/chota-vue-pinia/src/ui/atoms/Button/Button.component.vue -->
<template>
  <button v-if="isLoading" :disabled="disabled" @click="$emit('onClick')" :class="getButtonClass()">
    <Loader width="2px" size="1.2rem" color="#fff" />
  </button>
  <button v-else :disabled="disabled" :type="type" @click="$emit('onClick')" :class="getButtonClass()">
    <slot />
  </button>
</template>

<script>
import { defineComponent } from 'vue'
import Loader from '../Loader/Loader.component.vue';

export default defineComponent({
  components: { Loader },
  props: ['isLoading', 'class', 'disabled', 'onClick', 'label', 'type'],
  methods: {
    getButtonClass() {
      return `${this.class} loading-button`;
    },
  },
})
</script>

<style scoped src="./Button.style.css"></style>

Web Components

// templates/chota-wc-saga/src/ui/atoms/Button/Button.component.js
import { html } from "lit";
import style from './Button.style';
import "../Loader/app-loader";
import useComputedStyles from "../../../utils/theme/hooks/useComputedStyles";
import emit from "../../../utils/events/emit";

export default function Button(props) {
  useComputedStyles(this, [style]);
  if (props.isLoading) {
    return html`
      <button type="button" class=${`${props.classes} loading-button`}>
        <app-loader .width=${'2px'} .size=${'1.2rem'} .color=${'#fff'}>Loading...</app-loader>
      </button>
    `;
  }
  return html`
    <button type="button"
      class="${props.classes}"
      @click="${() => emit(this, "onClick", props)}">
      <slot></slot>
    </button>
  `;
}

A few things worth comparing across the tabs:

Advanced Example: Icon Atom with a Path Lookup Table

A small icon atom showing how a single component can render any one of N variants from a static lookup, while keeping the prop surface tiny:

// Icon.js

import React from "react";

const ICON_PATHS = {
  search: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z",
  user: "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z",
  close: "M6 18L18 6M6 6l12 12",
  check: "M5 13l4 4L19 7"
};

const Icon = ({
  name,
  size = 24,
  color = "currentColor",
  className = "",
  ariaLabel
}) => {
  const pathData = ICON_PATHS[name];

  if (!pathData) {
    console.warn(`Icon "${name}" not found`);
    return null;
  }

  return (
    <svg
      width={size}
      height={size}
      viewBox="0 0 24 24"
      fill="none"
      stroke={color}
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      className={`icon ${className}`}
      role="img"
      aria-label={ariaLabel || `${name} icon`}
    >
      <path d={pathData} />
    </svg>
  );
};

export default Icon;

Usage demonstrating composition:

// SearchButton.js (Molecule composed of atoms)

import React from "react";
import Button from "./Button";
import Icon from "./Icon";

const SearchButton = ({ onClick }) => {
  return (
    <Button onClick={onClick} label="">
      <Icon name="search" size={20} ariaLabel="Search" />
      <span>Search</span>
    </Button>
  );
};

Common Mistakes

1. Making Atoms Too Complex

Mistake: Adding business logic, API calls, or state management to atom components.

// ❌ BAD: Atom with too much responsibility
const Button = ({ userId }) => {
  const [loading, setLoading] = useState(false);

  const handleClick = async () => {
    setLoading(true);
    await fetch(`/api/users/${userId}/activate`);
    setLoading(false);
  };

  return <button onClick={handleClick}>{loading ? 'Loading...' : 'Activate'}</button>;
};
// ✅ GOOD: Atom focused on presentation
const Button = ({ onClick, label, loading = false, disabled = false }) => {
  return (
    <button onClick={onClick} disabled={disabled || loading}>
      {loading ? 'Loading...' : label}
    </button>
  );
};

Why it matters: Complex atoms become difficult to reuse, test, and maintain. Keep business logic in containers or parent components.

2. Inconsistent Prop Interfaces

Mistake: Using different prop names or patterns for similar atoms.

// ❌ BAD: Inconsistent naming
const Button = ({ clickHandler, text }) => { /* ... */ };
const Input = ({ onChange, value }) => { /* ... */ };
const Checkbox = ({ onUpdate, checked }) => { /* ... */ };
// ✅ GOOD: Consistent prop patterns
const Button = ({ onClick, label, ...props }) => { /* ... */ };
const Input = ({ onChange, value, ...props }) => { /* ... */ };
const Checkbox = ({ onChange, checked, ...props }) => { /* ... */ };

Why it matters: Consistent interfaces reduce cognitive load and make atoms predictable across your codebase.

3. Ignoring Accessibility

Mistake: Building atoms without ARIA labels, keyboard support, or semantic HTML.

// ❌ BAD: Not accessible
const IconButton = ({ onClick, icon }) => (
  <div onClick={onClick}>
    <Icon name={icon} />
  </div>
);
// ✅ GOOD: Accessible implementation
const IconButton = ({ onClick, icon, ariaLabel }) => (
  <button
    onClick={onClick}
    aria-label={ariaLabel}
    type="button"
  >
    <Icon name={icon} aria-hidden="true" />
  </button>
);

Why it matters: Atoms are used throughout your application. Accessibility issues in atoms multiply across every instance.

Quick Quiz

What is the primary characteristic that defines a component as an 'atom' in Atomic Design?

Should an atom component contain API calls and business logic to make it self-sufficient?

Which of these is a well-designed atom?

What's the main benefit of wrapping a native HTML element in an atom component instead of using it directly?

How should an atom handle visual variants like `primary`, `secondary`, `danger`?

References