<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Brian Bal</title><description>I am on a roll!</description><link>https://brianbal.com/</link><language>en-us</language><item><title>CSS Heavy Buttons</title><link>https://brianbal.com/blog/20250514-css-buttons/</link><guid isPermaLink="true">https://brianbal.com/blog/20250514-css-buttons/</guid><description>React buttons implemented mostly with css</description><pubDate>Wed, 14 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Lets try to implement a react button component that uses mostly css.&lt;/p&gt;
&lt;h3&gt;What We Need&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A root class &lt;code&gt;.css-button&lt;/code&gt; with CSS variables for colors, spacing, and animation.&lt;/li&gt;
&lt;li&gt;Expand on the root class for different button stats with: &lt;code&gt;:hover&lt;/code&gt;, &lt;code&gt;:active&lt;/code&gt;, &lt;code&gt;:focus&lt;/code&gt;, &lt;code&gt;[disabled]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A handful of variants &lt;code&gt;.primary&lt;/code&gt;, &lt;code&gt;.secondary&lt;/code&gt;, &lt;code&gt;.outline&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Optional icon and text wrappers.&lt;/li&gt;
&lt;li&gt;Easy theming with &lt;em&gt;zero&lt;/em&gt; extra logic.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Styles&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* CSS-heavy Button Component */
.css-button {
  /* Default variables */
  --button-bg-color: transparent;
  --button-border-color: #333;
  --button-text-color: #333;
  --button-padding: 10px 16px;
  --button-border-radius: 12px;
  --button-font-weight: 500;
  --button-font-size: 1rem;
  --button-font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
  --button-gap: 8px;
  --button-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  --button-hover-bg-color: #333;
  --button-hover-border-color: #333;
  --button-hover-text-color: white;
  --button-hover-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
  --button-active-bg-color: #000;
  --button-active-border-color: #000;
  --button-active-text-color: white;
  --button-active-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  --button-selected-bg-color: dodgerblue;
  --button-selected-border-color: dodgerblue;
  --button-selected-text-color: white;
  --button-focus-shadow: 0 0 0 3px rgba(30, 144, 255, 0.5);

  appearance: none;
  position: relative;
  display: flex;
  align-items: center;
  text-align: center;
  gap: var(--button-gap);
  padding: var(--button-padding);
  width: 100%;
  border-radius: var(--button-border-radius);
  font-weight: var(--button-font-weight);
  cursor: pointer;
  overflow: hidden;
  background-color: var(--button-bg-color);
  border: 1px solid var(--button-border-color);
  color: var(--button-text-color);
  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
  justify-content: center;
  box-shadow: var(--button-shadow);
  font-size: var(--button-font-size);
  font-weight: var(--button-font-weight);
  font-family: var(--button-font-family);
}

/* Icon container */
.css-button .icon-container {
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.3s ease;
}

/* Text container */
.css-button .text-container {
  font-size: 1rem;
  transition: transform 0.3s ease;
}

/* Hover state */
.css-button:hover {
  background-color: var(--button-hover-bg-color);
  color: var(--button-hover-text-color);
  border-color: var(--button-hover-border-color);
  transform: translateY(-2px);
  box-shadow: var(--button-hover-shadow);
}

.css-button:hover .icon-container {
  animation: pulse 0.5s ease infinite alternate;
}

/* Active/pressed state */
.css-button:active {
  background-color: var(--button-active-bg-color);
  border-color: var(--button-active-border-color);
  color: var(--button-active-text-color);
  transform: translateY(1px);
  box-shadow: var(--button-active-shadow);
}

.css-button:active .icon-container,
.css-button:active .text-container {
  transform: scale(0.95);
}

/* Selected state */
.css-button.selected {
  background-color: var(--button-selected-bg-color);
  border-color: var(--button-selected-border-color);
  color: var(--button-selected-text-color);
}

.css-button.selected::after {
  content: &apos;&apos;;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: radial-gradient(circle, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 70%);
  opacity: 0;
  animation: ripple 1.5s ease-out infinite;
}

/* Animations */
@keyframes pulse {
  0% {
    transform: scale(1);
  }
  100% {
    transform: scale(1.1);
  }
}

@keyframes ripple {
  0% {
    transform: scale(0.8);
    opacity: 0.5;
  }
  100% {
    transform: scale(1.5);
    opacity: 0;
  }
}

/* Focus state for accessibility */
.css-button:focus {
  outline: none;
}

/* Mobile optimizations */
@media (hover: none) and (pointer: coarse) {
  .css-button {
    -webkit-tap-highlight-color: transparent;
  }

  .css-button:hover {
    transform: none;
    box-shadow: var(--button-shadow);
  }
}

/* Button Types */
.css-button.primary {
  --button-bg-color: #007bff;
  --button-border-color: #007bff;
  --button-text-color: white;
  --button-hover-bg-color: #0056b3;
  --button-hover-border-color: #0056b3;
  --button-active-bg-color: #004085;
  --button-active-border-color: #004085;
  --button-selected-bg-color: #0056b3;
  --button-selected-border-color: #0056b3;
}

.css-button.secondary {
  --button-bg-color: #6c757d;
  --button-border-color: #6c757d;
  --button-text-color: white;
  --button-hover-bg-color: #5a6268;
  --button-hover-border-color: #5a6268;
  --button-active-bg-color: #4e555b;
  --button-active-border-color: #4e555b;
  --button-selected-bg-color: #5a6268;
  --button-selected-border-color: #5a6268;
}

.css-button.outline {
  --button-bg-color: transparent;
  --button-border-color: #6c757d;
  --button-text-color: #6c757d;
  --button-hover-bg-color: #6c757d;
  --button-hover-text-color: white;
  --button-active-bg-color: #5a6268;
  --button-active-text-color: white;
  --button-selected-bg-color: #6c757d;
  --button-selected-text-color: white;
}

.css-button.icon-only {
  --button-padding: 10px;
  --button-border-radius: 50%;
  --button-gap: 0;
  width: auto;
  justify-content: center;
}

.css-button.icon-only .text-container {
  display: none;
}

.css-button.large {
  --button-padding: 16px 24px;
  --button-font-size: 2rem;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ok that seems like a good start, now let&apos;s make the component.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &quot;./Button.css&quot;;

interface ButtonProps {
  className?: string
  onClick?: () =&amp;gt; void
  children?: React.ReactNode
}

export default function Button({
  className,
  onClick,
  children,
}: ButtonProps) {
  return (
    &amp;lt;button
      className={className}
      onClick={onClick}
    &amp;gt;
      {children}
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The component is just a passthrough for &lt;code&gt;className&lt;/code&gt;, &lt;code&gt;onClick&lt;/code&gt;, and &lt;code&gt;children&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Demo&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://brianbal.com/assets/posts/css-buttons/demo.png&quot; alt=&quot;css button demo&quot; /&gt;
Try out the &lt;a href=&quot;https://brianbal.com/assets/posts/css-buttons/demo/index.html&quot;&gt;live demo&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Whats Good&lt;/h3&gt;
&lt;h4&gt;Zero internal state&lt;/h4&gt;
&lt;p&gt;No extra renders, no JS hover logic, no touch hacks&lt;/p&gt;
&lt;h4&gt;Easy to theme&lt;/h4&gt;
&lt;p&gt;Override a few &lt;code&gt;--button-*&lt;/code&gt; variables and you&apos;re good to go.&lt;/p&gt;
&lt;h4&gt;Variant Explosion Control&lt;/h4&gt;
&lt;p&gt;Adding &lt;code&gt;.danger&lt;/code&gt; or &lt;code&gt;.success&lt;/code&gt; is a single rule, not a refactor. There is also a clear pattern for creating new variants.&lt;/p&gt;
&lt;h4&gt;Simple markup&lt;/h4&gt;
&lt;p&gt;Doesnt&apos;t get much simpler. Easier to integrate into pretty much anything.&lt;/p&gt;
&lt;h3&gt;Whats Not Awesome&lt;/h3&gt;
&lt;h4&gt;Extra States&lt;/h4&gt;
&lt;p&gt;Extra states like &lt;code&gt;selected&lt;/code&gt;, &lt;code&gt;loading&lt;/code&gt;, &lt;code&gt;danger&lt;/code&gt; still need manual class or ARIA toggles.&lt;/p&gt;
&lt;h4&gt;Wrappers&lt;/h4&gt;
&lt;p&gt;To get all the animation and feature consumers of the button must use &lt;code&gt;.icon-wrapper&lt;/code&gt; and &lt;code&gt;.text-wrapper&lt;/code&gt; classes.&lt;/p&gt;
&lt;h4&gt;CSS Specificity Creep&lt;/h4&gt;
&lt;p&gt;Variant rules rely on redefining the same &lt;code&gt;--button-*&lt;/code&gt; props multiple times. If you later add &lt;code&gt;.danger.outline&lt;/code&gt; or &lt;code&gt;.primary.large&lt;/code&gt;, precedence chains get tricky.&lt;/p&gt;
&lt;h2&gt;Compare with &lt;a href=&quot;https://brianbal.com/blog/20250512-the-problem-with-buttons/&quot;&gt;Basic Buttons&lt;/a&gt;&lt;/h2&gt;
&lt;h4&gt;Lines of Component Code&lt;/h4&gt;
&lt;p&gt;The CSS heavy button is about as small a component as it can get.&lt;/p&gt;
&lt;h4&gt;Reactivity&lt;/h4&gt;
&lt;p&gt;CSS buttons use the browser states and this feels like the correct way to handle it.&lt;/p&gt;
&lt;h4&gt;Extensibility&lt;/h4&gt;
&lt;p&gt;To extend the basic button requires creating a new button component for every variant.
Using CSS classes to override the styles can lead to complex class and precedence chains, but it is also very extensible.&lt;/p&gt;
&lt;h4&gt;Themability&lt;/h4&gt;
&lt;p&gt;The css heavy button is so much easier to theme.&lt;/p&gt;
&lt;h4&gt;Overall&lt;/h4&gt;
&lt;p&gt;The two different approaches feel similar to click and use.
The CSS heavy version is lighter, simpler, and scales variants better.
This is the better direction for a design‑system foundation.&lt;/p&gt;
&lt;h3&gt;Next Up&lt;/h3&gt;
&lt;p&gt;Leaning on CSS and the DOM to create a React Button component worked out pretty well. Its not a perfect solution, but it is definitely a better direction than the first attempt.&lt;/p&gt;
&lt;p&gt;Next we can look in a different direction and try a more JSX heavy approach.&lt;/p&gt;
</content:encoded></item><item><title>The Problem with Buttons</title><link>https://brianbal.com/blog/20250512-the-problem-with-buttons/</link><guid isPermaLink="true">https://brianbal.com/blog/20250512-the-problem-with-buttons/</guid><description>Why are buttons so hard to get right?</description><pubDate>Mon, 12 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Creating buttons seems like a simple task, but it can be a bit tricky to get right.&lt;/p&gt;
&lt;h3&gt;Lot of States&lt;/h3&gt;
&lt;p&gt;Even a &quot;simple&quot; button has to respond to a bunch of states:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;default&lt;/li&gt;
&lt;li&gt;hover&lt;/li&gt;
&lt;li&gt;active&lt;/li&gt;
&lt;li&gt;focus&lt;/li&gt;
&lt;li&gt;disabled&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then your app adds its own flavor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;selected&lt;/li&gt;
&lt;li&gt;primary&lt;/li&gt;
&lt;li&gt;secondary&lt;/li&gt;
&lt;li&gt;loading&lt;/li&gt;
&lt;li&gt;danger&lt;/li&gt;
&lt;li&gt;success&lt;/li&gt;
&lt;li&gt;outline&lt;/li&gt;
&lt;li&gt;icon&lt;/li&gt;
&lt;li&gt;icon-only&lt;/li&gt;
&lt;li&gt;waitingForServerToStopFlaking...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And multiply those accross device types like desktop, tablet, and phone and there is a small explosion of possible states and variants.&lt;/p&gt;
&lt;h3&gt;Let&apos;s Build One Anyway&lt;/h3&gt;
&lt;p&gt;Just gonna vibe code a basic React button and walk through where it works—and where it breaks down.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://brianbal.com/assets/posts/the-problem-with-buttons/demo.png&quot; alt=&quot;Nice Buttons&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://brianbal.com/assets/posts/the-problem-with-buttons/demo/index.html&quot;&gt;live demo&lt;/a&gt; to see these buttons in action!&lt;/p&gt;
&lt;p&gt;Button.tsx&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useState, useEffect } from &quot;react&quot;;
import type { LucideIcon } from &quot;lucide-react&quot;;

interface ButtonProps {
  icon: LucideIcon;
  text: string;
  selected?: boolean;
  onClick?: () =&amp;gt; void;
}

const Button = ({
  icon: Icon,
  text,
  selected = false,
  onClick,
}: ButtonProps) =&amp;gt; {
  const [isHovered, setIsHovered] = useState(false);
  const [isPressed, setIsPressed] = useState(false);

  useEffect(() =&amp;gt; {
    if (!selected) {
      setIsHovered(false);
      setIsPressed(false);
    }
  }, [selected]);

  const handleMouseEnter = () =&amp;gt; {
    setIsHovered(true);
  };

  const handleMouseLeave = () =&amp;gt; {
    setIsHovered(false);
    setIsPressed(false);
  };

  const handleMouseDown = () =&amp;gt; {
    setIsPressed(true);
  };

  const handleMouseUp = () =&amp;gt; {
    setIsPressed(false);
  };

  const handleTouchStart = () =&amp;gt; {
    setIsPressed(true);
    setIsHovered(true);
  };

  const handleTouchEnd = () =&amp;gt; {
    setIsPressed(false);
    setTimeout(() =&amp;gt; setIsHovered(false), 150);

    if (onClick) onClick();
  };

  const handleTouchCancel = () =&amp;gt; {
    setIsPressed(false);
    setIsHovered(false);
  };

  const handleClick = () =&amp;gt; {
    if (window.matchMedia(&quot;(hover: hover)&quot;).matches) {
      if (onClick) onClick();
    }
  };

  const getButtonStyles = () =&amp;gt; {
    if (selected) {
      return {
        background: &quot;dodgerblue&quot;,
        border: &quot;1px solid dodgerblue&quot;,
        color: &quot;white&quot;,
      };
    }
    if (isPressed) {
      return {
        background: &quot;black&quot;,
        border: &quot;1px solid black&quot;,
        color: &quot;white&quot;,
      };
    }
    if (isHovered) {
      return {
        background: &quot;black&quot;,
        border: &quot;1px solid black&quot;,
        color: &quot;white&quot;,
      };
    }
    return {
      background: &quot;transparent&quot;,
      border: &quot;1px solid black&quot;,
      color: &quot;black&quot;,
    };
  };

  const buttonStyles = getButtonStyles();

  const getAnimationClass = () =&amp;gt; {
    if (selected) return &quot;selected&quot;;
    if (isPressed) return &quot;pressed&quot;;
    if (isHovered) return &quot;hovered&quot;;
    return &quot;&quot;;
  };

  return (
    &amp;lt;button
      className={`nice-button ${getAnimationClass()}`}
      style={{
        backgroundColor: buttonStyles.background,
        borderColor: buttonStyles.border,
        color: buttonStyles.color,
        display: &quot;flex&quot;,
        alignItems: &quot;center&quot;,
        gap: &quot;8px&quot;,
        padding: &quot;10px 16px&quot;,
        borderRadius: &quot;12px&quot;,
        cursor: &quot;pointer&quot;,
        transition: &quot;all 0.2s ease&quot;,
      }}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      onMouseDown={handleMouseDown}
      onMouseUp={handleMouseUp}
      onTouchStart={handleTouchStart}
      onTouchEnd={handleTouchEnd}
      onTouchCancel={handleTouchCancel}
      onClick={handleClick}
    &amp;gt;
      &amp;lt;span className=&quot;icon-wrapper&quot;&amp;gt;
        &amp;lt;Icon size={20} color={buttonStyles.color} /&amp;gt;
      &amp;lt;/span&amp;gt;
      &amp;lt;span className=&quot;text-wrapper&quot;&amp;gt;{text}&amp;lt;/span&amp;gt;
    &amp;lt;/button&amp;gt;
  );
};

export default Button;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Button.css&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Button */
.nice-button {
  position: relative;
  overflow: hidden;
  transition: background-color 0.3s, border-color 0.3s, color 0.3s;
  width: 100%;
  justify-content: flex-start;
  font-weight: 500;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}

/* icon */
.icon-wrapper {
  margin-right: 4px;
}

/* text */
.text-wrapper {
  font-size: 1rem;
}

/* Wiggle animation for hover */
.nice-button.hovered .icon-wrapper,
.nice-button.hovered .text-wrapper {
  animation: wiggle 0.5s ease;
}

/* Press in animation */
.nice-button.pressed .icon-wrapper,
.nice-button.pressed .text-wrapper {
  transform: scale(0.95);
  transition: transform 0.1s ease-in;
}

/* Selected state animations */
.nice-button.selected .icon-wrapper,
.nice-button.selected .text-wrapper {
  animation: pop 0.3s ease;
}

/* Ensure smooth transitions for icon and text */
.icon-wrapper,
.text-wrapper {
  transition: transform 0.2s ease, color 0.2s ease;
  display: inline-flex;
  align-items: center;
}

/* Add hover effect */
.nice-button:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}

/* Add active effect */
.nice-button:active {
  transform: translateY(1px);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* Enhanced mobile touch styles */
@media (hover: none) and (pointer: coarse) {
  .nice-button {
    -webkit-tap-highlight-color: transparent;
  }

  .nice-button.pressed {
    transform: scale(0.97);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    transition: transform 0.1s ease-in, box-shadow 0.1s ease-in;
  }

  .nice-button.pressed .icon-wrapper,
  .nice-button.pressed .text-wrapper {
    transform: scale(0.95);
    transition: transform 0.1s ease-in;
  }
}

/* Wiggle animation keyframes */
@keyframes wiggle {
  0% {
    transform: rotate(0deg);
  }
  25% {
    transform: rotate(-5deg);
  }
  50% {
    transform: rotate(0deg);
  }
  75% {
    transform: rotate(5deg);
  }
  100% {
    transform: rotate(0deg);
  }
}

/* Pop animation for color change */
@keyframes pop {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.1);
  }
  100% {
    transform: scale(1);
  }
}

/* Smooth transition styles for button states */
@keyframes smooth-color-change {
  from {
    opacity: 0.8;
  }
  to {
    opacity: 1;
  }
}

/* Mobile */
@media screen and (max-width: 540px) {
  .nice-button:hover {
    transform: none;
  }

  .nice-button:active {
    transform: none;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Strengths&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Touch Support&lt;/strong&gt;: Handles both mouse and touch well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good UX Feedback&lt;/strong&gt;: Animations for hover, press, and selection add nice tactile feel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Component Isolation&lt;/strong&gt;: Doesn’t rely on global context—fully self-contained.&lt;/p&gt;
&lt;p&gt;Honestly, it’s a nice button. It feels good to click. You want to click it.&lt;/p&gt;
&lt;h3&gt;Weaknesses&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Too Much Internal State&lt;/strong&gt;: Hover, press, and selection are tracked in React state when the browser could handle most of this with :hover and :active.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inline Styles&lt;/strong&gt;: Makes it hard to theme or override styles externally.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;State Explosion&lt;/strong&gt;: Adding more states like loading, disabled, or danger requires touching the component logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Doesn&apos;t the DOM do that?&lt;/strong&gt;: We’re recreating what the browser already does well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Accessibility&lt;/strong&gt;: No aria-*, no keyboard handling, nothing to help screen readers.&lt;/p&gt;
&lt;p&gt;It works, but it’s already harder to extend than it should be.&lt;/p&gt;
&lt;h3&gt;Better Button Ideas&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Option 1: CSS-Heavy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let the browser and CSS do what they’re good at. One root class like &lt;code&gt;.nice-button&lt;/code&gt;, then variants like &lt;code&gt;.primary&lt;/code&gt;, &lt;code&gt;.secondary&lt;/code&gt;, etc. Use CSS variables to theme, use &lt;code&gt;:hover&lt;/code&gt;, &lt;code&gt;:active&lt;/code&gt;, and &lt;code&gt;:focus&lt;/code&gt; for state.&lt;/p&gt;
&lt;p&gt;No internal state. No inline styles. Just clean, scalable CSS.&lt;/p&gt;
&lt;p&gt;✅ Great for design systems.&lt;br /&gt;
✅ Easy to theme.&lt;br /&gt;
❌ Harder to dynamically change content (like icons or labels on loading).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 2: JSX-Heavy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Leaning all the way into React. You define your states in JSX:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Button&amp;gt;
  &amp;lt;ButtonState type=&quot;default&quot;&amp;gt;Save&amp;lt;/ButtonState&amp;gt;
  &amp;lt;ButtonState type=&quot;hover&quot;&amp;gt;🖱 Hovering&amp;lt;/ButtonState&amp;gt;
  &amp;lt;ButtonState type=&quot;loading&quot;&amp;gt;⏳ Saving...&amp;lt;/ButtonState&amp;gt;
&amp;lt;/Button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React handles rendering the right content based on internal state. You get full control.&lt;/p&gt;
&lt;p&gt;✅ Great for animation, dynamic content, marketing-style buttons.&lt;br /&gt;
❌ Boilerplate-heavy, hard to scale across a team.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 3: Hybrid&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Use class-based styling for layout, color, and animation but let the component handle special states like loading, selected, etc. Maybe it swaps icons. Maybe it disables clicks. But most of the work happens in CSS.&lt;/p&gt;
&lt;p&gt;✅ Composable, themeable, extendable.&lt;br /&gt;
✅ Easy to maintain.&lt;br /&gt;
❌ Still needs some discipline to avoid getting bloated.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Turns out making an awesome button isn’t that simple, but doesn’t have to be complicated either.&lt;/p&gt;
&lt;p&gt;Over the next few posts, I’ll break down different the different approaches above and see what works well and what doesn’t.&lt;/p&gt;
</content:encoded></item><item><title>Hello World</title><link>https://brianbal.com/blog/20250507-first-post/</link><guid isPermaLink="true">https://brianbal.com/blog/20250507-first-post/</guid><description>First post!</description><pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;Node.js&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;Hello, world!&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;TypeScript&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;Hello, world!&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Rust&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;fn main() {
    println!(&quot;Hello, world!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;C++&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

int main() {
    std::cout &amp;lt;&amp;lt; &quot;Hello, world!&quot; &amp;lt;&amp;lt; std::endl;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Python&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;print(&quot;Hello, world!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Go&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package main

import &quot;fmt&quot;

func main() {
    fmt.Println(&quot;Hello, world!&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Swift&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import Foundation

print(&quot;Hello, world!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Kotlin&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;fun main() {
    println(&quot;Hello, world!&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;SQL&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SELECT &apos;Hello, world!&apos; AS greeting;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Bash&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env bash
echo &quot;Hello, world!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Happy coding! 🎉&lt;/p&gt;
</content:encoded></item></channel></rss>