Component Abstraction: Writing Reusable UI with Tailwind + React

Learn how to create reusable UI components with React and Tailwind CSS, enhancing consistency, efficiency, and maintainability in web development.

Web Development
Jul 20, 2025
Component Abstraction: Writing Reusable UI with Tailwind + React

Want to build better, reusable UI components? Combining React and Tailwind CSS makes this process simpler and more efficient. Here’s what you’ll learn in this guide:

  • Why Component Abstraction Matters: Avoid duplicate code, inconsistent styles, and maintenance headaches by breaking UI into reusable pieces.
  • How React and Tailwind Work Together: React handles structure; Tailwind simplifies styling with utility classes.
  • Key Techniques: Use props, composition, and Tailwind’s utility classes to create adaptable components.
  • Practical Examples: Build reusable buttons, cards, and forms with dynamic styling and responsive design.

This guide also covers best practices for organizing your code, testing components, and maintaining consistency across your UI system. Whether you’re new or experienced, these tips will help you create scalable, maintainable design systems.

Component Abstraction Fundamentals

What Is Component Abstraction?

Component abstraction is all about simplifying complexity by exposing only what’s necessary. Think of it like using a smartphone - you don’t need to understand the intricate circuitry to make a call, send a text, or browse the web. The phone’s interface abstracts all that complexity, leaving you with an easy-to-use interface.

In React and Tailwind development, this concept works the same way. You build components that handle the complicated details internally, so developers only need to interact with the essential parts. As Cayla Horsey from Power puts it:

“Abstraction is a key concept in programming, helping us handle complexity by breaking down large systems into manageable, reusable pieces.”

This approach simplifies logic, makes components reusable, and reduces maintenance headaches. For example, instead of writing button styles repeatedly, you can create a single Button component that manages all variations internally.

The real strength of abstraction lies in its ability to reduce dependencies between system components, making it easier to replace or upgrade technologies without disrupting the overall system.

Main Benefits of UI Component Abstraction

Component abstraction offers several advantages that can directly improve your development process and project outcomes.

  • Reusability: A well-abstracted component can be reused across your app without rewriting code. In fact, these components can often be adapted for use in different projects with minimal effort.
  • Consistency: Abstracted components ensure uniformity. Every instance of a component looks and behaves the same, eliminating issues like inconsistent spacing, colors, or interactions across your app.
  • Faster Development: With reusable and consistent components, developers spend less time writing repetitive code and more time solving unique challenges. As one expert explains:

“Abstraction helps manage complexity. In software development, projects can quickly become overwhelming with countless components interacting in intricate ways. Without abstraction, programmers would be bogged down by the minutiae, making it nearly impossible to create, maintain, or scale applications.”

Additionally, abstraction frees up mental bandwidth, allowing developers to focus on higher-level problems like architecture and user experience. It also encourages teamwork by creating clear boundaries between components, making it easier for teams to collaborate without stepping on each other’s toes.

Using Props and Composition in React

React

React’s props and composition are essential tools for implementing abstraction. They allow developers to build reusable, flexible UI components.

Composition involves combining smaller, focused components to create more complex ones. This lets you mix and match components without altering their internal code. For example, React’s children prop allows you to pass content into a component without knowing its structure beforehand. This makes components highly adaptable.

Here’s a simple example of a Card component that uses composition:

const Card = ({ title, subTitle, children, actions = ['like', 'comment'] }) => (
  <div className="card">
    <div className="card-heading">
      <h2>{title}</h2>
      <p>{subTitle}</p>
    </div>
    <div className="card-main">{children}</div>
    <div className="card-actions">
      {actions.map((action) => (
        <button key={action}>{action}</button>
      ))}
    </div>
  </div>
);

This component is versatile. By using the children prop, you can display various types of content:

const App = () => (
  <div className="App">
    <h1>React Composition</h1>
    <div className="cards">
      <Card title="Craft Beer" subTitle="Most Popular Styles">
        <ul>
          <li>IPA</li>
          <li>Sour</li>
          <li>Stout</li>
        </ul>
      </Card>
    </div>
  </div>
);

The same Card component can now handle lists, paragraphs, images, or anything else you pass as children.

You can also create specialized components by extending general ones. For instance, a PhotoCard component can build on the Card component:

const PhotoCard = ({ src, alt, children, ...rest }) => (
  <Card {...rest}>
    <img src={src} alt={alt} />
    {children}
  </Card>
);

Here, the ...rest operator ensures the PhotoCard inherits all Card-specific props like title and subTitle, while adding functionality for images.

Composition in Forms

Another great use case for composition is form components:

const Form = ({ onSubmit, children }) => (
  <form onSubmit={onSubmit}>{children}</form>
);

const InputField = ({ value, onChange, children }) => (
  <label>
    {children}
    <input
      type="text"
      value={value}
      onChange={(event) => onChange(event.target.value)}
    />
  </label>
);

These components work together seamlessly to create flexible forms without tightly coupling their structure and content.

As the React documentation highlights:

“Composition is a very fundamental and powerful concept in React, as it makes it easier to manage your codebase by avoiding repetition, while providing flexibility in situations where you don’t know what you might want to render inside a component ahead of time, or if you want to customize a component into its more specialized variation.”

This is especially useful with Tailwind CSS, where you can maintain consistent utility class patterns while still allowing for customizations at the component level.

Creating Reusable Components with Tailwind and React

Building on the foundational concepts of abstraction, this section outlines practical ways to combine the adaptability of React with the utility-first approach of Tailwind CSS.

How to Structure Components for Reusability

Begin by crafting simple markup, and as you notice recurring patterns, extract them into reusable components.

To create components that are easy to reuse, follow these principles:

  • Define UI states clearly: Instead of juggling multiple boolean flags, use a single status value like 'idle', 'loading', 'success', or 'error' to represent different states.
  • Avoid passing raw CSS classes as props: Rather than using something like className="btn-primary", introduce explicit variant props, such as variant="primary", and map them internally to specific class combinations.
  • Keep components self-contained: Use local state to ensure components are independent and easy to test.

When designing components, consider these preferences:

  • Derive data from props whenever possible instead of storing it in state.
  • For components with fixed behavior, minimize unnecessary props.
  • Create separate components for distinct variations instead of overloading one component with too many options.
  • Manage data through child elements when appropriate.

Functional components are often a better choice than class components because they are simpler, perform better, and work seamlessly with React Hooks.

For better organization, consider using atomic design principles. Structure your components in the following hierarchy:

  • Atoms: Basic elements like buttons or input fields.
  • Molecules: Small groups of atoms, such as search forms.
  • Organisms: Larger sections like headers or footers.
  • Templates: Page layouts with placeholders.
  • Pages: Specific instances of templates for individual screens.

This hierarchical approach makes it easier to locate, update, and maintain components.

Using Tailwind’s Utility Classes

Tailwind’s utility-first styling requires a shift in mindset compared to traditional CSS frameworks. Instead of writing custom styles, you build designs by combining small, single-purpose utility classes.

  • Centralize design decisions: Use the tailwind.config.js file to define consistent colors, spacing, typography, and other design tokens.

  • Organize classes for clarity: A common practice is to order classes based on layout, sizing, spacing, typography, visual styling, and interactive states. For instance:

    <button className="flex items-center px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
      Submit
    </button>
    
  • Automate class sorting: Tools like Prettier or the Headwind extension for Visual Studio Code can help maintain consistent class order effortlessly.

  • Reduce repetition: Encapsulate frequently used utility class combinations into reusable components to avoid duplicating code.

For conditional logic, libraries like classnames or clsx can simplify the process of dynamically applying utility classes based on variables or props.

Use group utilities to manage interactive states across multiple elements. For example, hovering over a card might change both its background color and the text color of its child elements.

Avoid overusing the @apply directive. Even Adam Wathan, the creator of Tailwind CSS, has noted that @apply can lead to debugging headaches and should be used sparingly. Instead, stick to utility classes directly in your JSX for better clarity and maintainability.

Lastly, include semantic identifiers like data-testid or aria-label in your components to preserve meaning without cluttering your utility classes.

Implementing Conditional Styling and Variants

Conditional styling allows components to adapt to different states and use cases, making them versatile and reusable. This approach ties into the principles of composition and scalability.

  • Dynamic class binding: Use template literals or libraries like classnames to apply classes based on props or state. Here’s an example:

    const Button = ({ variant, size, disabled, children }) => {
      const baseClasses = "font-medium rounded-md focus:outline-none focus:ring-2";
    
      const variantClasses = {
        primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
        secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500",
        danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",
      };
    
      const sizeClasses = {
        sm: "px-3 py-1.5 text-sm",
        md: "px-4 py-2 text-base",
        lg: "px-6 py-3 text-lg",
      };
    
      const classes = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${
        disabled ? "opacity-50 cursor-not-allowed" : ""
      }`;
    
      return (
        <button className={classes} disabled={disabled}>
          {children}
        </button>
      );
    };
    
  • Responsive prefixes: Tailwind’s responsive utilities (e.g., sm:, md:, lg:) make it easy to adjust styles for different screen sizes:

    const Card = ({ children }) => (
      <div className="p-4 bg-white rounded-lg shadow-sm sm:p-6 md:p-8 lg:max-w-2xl">
        {children}
      </div>
    );
    
  • Stacking variants: Combine utilities for complex interactive behaviors. For example, hover:focus:bg-blue-800 applies styles when both hover and focus states are active.

When conflicting utilities arise, use conditional logic to ensure only the appropriate classes are applied. This keeps your components clean and adaptable to various scenarios.

The key is balancing the direct use of utility classes with tailored abstractions, ensuring your components remain both maintainable and flexible for diverse styling needs.

Practical Example: Building a Card Component

Let’s bring the concepts we’ve discussed to life by creating a fully functional card component from scratch. This example will show how to structure reusable components, incorporate dynamic props, and use Tailwind’s utility classes effectively.

Creating the Basic Card Structure

Start by laying down a simple foundation before turning it into a reusable component. Here’s how you can set up the basic structure:

const Card = () => {
  return (
    <div className="bg-white rounded-lg shadow-md">
      <div className="p-6">
        <h3 className="text-lg font-semibold text-gray-900">
          Sample Card Title
        </h3>
        <p className="mt-2 text-gray-600">
          This is a basic card description that provides context.
        </p>
      </div>
    </div>
  );
};

This simple setup uses Tailwind’s utility classes to create a clean design. For example:

  • bg-white sets a white background.
  • rounded-lg adds smooth, rounded corners.
  • shadow-md introduces subtle depth.
  • p-6 ensures consistent padding inside the card.

With this base, you can already see the component taking shape. Next, let’s make it dynamic.

Adding Dynamic Content with Props

To make the card more versatile, we’ll use props. Props in React allow you to pass data into components, making them customizable. Here’s how we can update the card:

const Card = ({ title, description, imageUrl, badge, onClick }) => {
  return (
    <div 
      className="bg-white rounded-lg shadow-md cursor-pointer transition-shadow hover:shadow-lg"
      onClick={onClick}
    >
      {imageUrl && (
        <img 
          src={imageUrl} 
          alt={title}
          className="w-full h-48 object-cover rounded-t-lg"
        />
      )}
      <div className="p-6">
        {badge && (
          <span className="inline-block px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 rounded-full mb-3">
            {badge}
          </span>
        )}
        <h3 className="text-lg font-semibold text-gray-900 mb-2">
          {title}
        </h3>
        <p className="text-gray-600">
          {description}
        </p>
      </div>
    </div>
  );
};

This version introduces props like title, description, imageUrl, badge, and onClick, allowing you to dynamically control the card’s content and behavior. For added safety, you can define the prop types using TypeScript:

interface CardProps {
  title: string;
  description: string;
  imageUrl?: string;
  badge?: string;
  onClick?: () => void;
}

const Card: React.FC<CardProps> = ({ title, description, imageUrl, badge, onClick }) => {
  // Component logic here
};

Styling with Tailwind Utilities

Now that the structure and content are dynamic, let’s refine the design with Tailwind utilities. This example incorporates responsive design, interactive states, and conditional styling for themes:

const Card = ({ title, description, imageUrl, badge, variant = 'default' }) => {
  const variantStyles = {
    default: 'bg-white border border-gray-200',
    featured: 'bg-gradient-to-br from-blue-50 to-indigo-100 border border-blue-200',
    dark: 'bg-gray-800 border border-gray-700 text-white'
  };

  const badgeStyles = {
    default: 'bg-gray-100 text-gray-800',
    featured: 'bg-blue-100 text-blue-800',
    dark: 'bg-gray-700 text-gray-300'
  };

  return (
    <div className={`
      ${variantStyles[variant]}
      rounded-xl shadow-sm hover:shadow-md 
      transition-all duration-200 ease-in-out
      transform hover:-translate-y-1
      max-w-sm mx-auto
      md:max-w-md lg:max-w-lg
    `}>
      {imageUrl && (
        <div className="relative overflow-hidden rounded-t-xl">
          <img 
            src={imageUrl} 
            alt={title}
            className="w-full h-48 sm:h-56 object-cover transition-transform duration-300 hover:scale-105"
          />
        </div>
      )}

      <div className="p-4 sm:p-6">
        {badge && (
          <span className={`
            inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium mb-3
            ${badgeStyles[variant]}
          `}>
            {badge}
          </span>
        )}

        <h3 className={`
          text-lg sm:text-xl font-semibold mb-2 line-clamp-2
          ${variant === 'dark' ? 'text-white' : 'text-gray-900'}
        `}>
          {title}
        </h3>

        <p className={`
          text-sm sm:text-base line-clamp-3
          ${variant === 'dark' ? 'text-gray-300' : 'text-gray-600'}
        `}>
          {description}
        </p>
      </div>
    </div>
  );
};

This approach highlights some key Tailwind features:

  • Responsive Design: Classes like sm: and md: adjust styles for different screen sizes (e.g., sm:p-6).
  • Interactive States: Effects like hover:shadow-md and hover:-translate-y-1 add smooth interactions.
  • Conditional Styling: Variants allow you to switch between themes (e.g., default, featured, dark).
  • Typography Control: Utilities like text-lg and line-clamp-2 create a clear hierarchy and prevent text overflow.

Here’s how you can use these cards:

// Basic card
<Card 
  title="Getting Started with React" 
  description="Learn the fundamentals of React development"
/>

// Featured card with image
<Card 
  title="Advanced Component Patterns" 
  description="Master complex React patterns and best practices"
  imageUrl="/images/react-patterns.jpg"
  badge="Premium"
  variant="featured"
/>

// Dark theme card
<Card 
  title="State Management Guide" 
  description="Comprehensive guide to managing state in React applications"
  badge="New"
  variant="dark"
/>

This flexible structure lets you create visually appealing cards tailored to different use cases. Whether you’re building a simple info card or a premium feature highlight, this component has you covered.

Best Practices for Scalable UI Systems

Building a scalable UI system with Tailwind CSS and React requires a strong foundation to ensure growth, maintain consistency, and simplify debugging. Below are practical tips to help you create a system that not only works well now but also evolves seamlessly as your project grows.

How to Organize Your Codebase

Organizing your code by feature rather than file type can make a world of difference. By grouping related components, hooks, and utilities together, you keep everything for a specific part of your application in one place. This structure enhances maintainability and clarity.

Here’s an example of how you might structure your project:

src/
├── components/
│   ├── ui/           # Base UI components (Button, Card, Input)
│   ├── layout/       # Layout components (Header, Sidebar, Footer)
│   └── common/       # Shared components across features
├── features/
│   ├── auth/
│   │   ├── components/
│   │   ├── hooks/
│   │   └── utils/
│   └── dashboard/
│       ├── components/
│       ├── hooks/
│       └── utils/
├── styles/
│   ├── globals.css
│   └── components.css
└── config/
    └── tailwind.config.js

“Pentakota Avilash suggests following atomic design principles when organizing components. This means structuring your components as atoms (basic elements like buttons), molecules (simple combinations like search forms), organisms (complex combinations like headers), templates (page layouts), and pages (specific instances)“.

To keep your design system centralized and consistent, use the tailwind.config.js file to define design tokens like colors, spacing, and fonts:

module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#eff6ff',
          500: '#3b82f6',
          900: '#1e3a8a'
        }
      },
      spacing: {
        '18': '4.5rem',
        '88': '22rem'
      },
      fontFamily: {
        'display': ['Inter', 'sans-serif']
      }
    }
  }
}

By organizing your codebase and design tokens this way, you create a system that’s easier to manage and scale.

Creating Consistent Component Design

Once your project is well-structured, focus on maintaining consistency in component design. A lack of clear guidelines can lead to fragmented implementations, which can hurt the user experience.

One way to ensure consistency is by following a logical order for Tailwind classes. Here’s a recommended class order to improve readability:

CategoryDescriptionExamples
LayoutDisplay and positioningflex, grid, absolute
SizingWidth and heightw-full, h-48, max-w-md
SpacingMargin and paddingp-4, mt-2, mx-auto
TypographyText stylingtext-lg, font-bold, text-center
VisualColors and bordersbg-white, border, shadow-md
InteractiveHover and focus stateshover:bg-gray-100, focus:ring-2

To make this process even easier, use the Prettier plugin for Tailwind CSS to automate class sorting:

npm install -D prettier prettier-plugin-tailwindcss

Add the plugin to your Prettier configuration:

{
  "plugins": ["prettier-plugin-tailwindcss"]
}

Additionally, abstract commonly used patterns into reusable components. For example, instead of repeating the same button classes across your codebase, create a dedicated Button component:

const Button = ({ children, variant = 'primary', ...props }) => {
  const baseClasses = 'px-4 py-2 rounded-md font-medium focus:ring-2 focus:ring-offset-2';
  const variantClasses = {
    primary: 'bg-blue-500 text-white hover:bg-blue-600 focus:ring-blue-500',
    secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500'
  };

  return (
    <button className={`${baseClasses} ${variantClasses[variant]}`} {...props}>
      {children}
    </button>
  );
};

Document your design system thoroughly. A comprehensive style guide with design principles, component usage, and coding standards ensures all team members stay on the same page.

Testing and Debugging Components

After organizing and standardizing your components, thorough testing and debugging are essential to maintain system reliability and prevent unexpected issues.

For behavior-focused testing, use React Testing Library and Jest. These tools help you test how users interact with your components rather than internal implementation details:

import { render, screen, fireEvent } from '@testing-library/react';
import { Card } from './Card';

test('card displays title and description correctly', () => {
  render(
    <Card 
      title="Test Title" 
      description="Test description" 
    />
  );

  expect(screen.getByText('Test Title')).toBeInTheDocument();
  expect(screen.getByText('Test description')).toBeInTheDocument();
});

test('card calls onClick when clicked', () => {
  const handleClick = jest.fn();
  render(
    <Card 
      title="Test Title" 
      description="Test description"
      onClick={handleClick}
    />
  );

  fireEvent.click(screen.getByText('Test Title'));
  expect(handleClick).toHaveBeenCalledTimes(1);
});

For visual regression testing, tools like Storybook and Chromatic are invaluable. Storybook allows you to isolate and test components visually, while Chromatic captures screenshots for each pull request to highlight changes.

Here’s an example of setting up Storybook stories:

// Card.stories.js
export default {
  title: 'Components/Card',
  component: Card,
};

export const Default = {
  args: {
    title: 'Default Card',
    description: 'This is a default card example'
  }
};

export const WithImage = {
  args: {
    title: 'Card with Image',
    description: 'This card includes an image',
    imageUrl: '/sample-image.jpg'
  }
};

export const Featured = {
  args: {
    title: 'Featured Card',
    description: 'This is a featured card variant',
    variant: 'featured',
    badge: 'New'
  }
};

Finally, use tools like ESLint to enforce coding standards and catch common mistakes early. For debugging, Chrome Developer Tools and React Developer Tools are indispensable. For projects using Redux, Redux DevTools provides additional insights into state management.

When working with complex responsive designs, consider creating separate components for different screen sizes instead of relying on conditional class names:

// Instead of managing everything in one component
const ComplexComponent = () => {
  return (
    <div className="block md:hidden lg:flex xl:grid">
      {/* Component content */}
    </div>
  );
};

Conclusion: Building Better UI Components

Using Tailwind CSS with React reshapes how developers approach UI design. Together, they boost productivity, ensure consistency, and make maintaining codebases easier.

Key Points to Keep in Mind

The utility-first approach changes how you think about styling. Instead of writing custom CSS or relying on prebuilt libraries, you gain precise control over each design element while keeping your application visually consistent. Teams adopting this method have reported a 30% increase in development speed.

Tailwind also offers built-in performance optimizations. With PurgeCSS, unused styles are automatically removed, leading to smaller CSS files and faster page loads.

Responsive design becomes straightforward with breakpoint-prefixed utilities, allowing you to create layouts that adapt seamlessly.

Encapsulation improves when styles are tied to utility classes rather than scattered across external stylesheets. This makes components modular and reusable, perfect for React applications.

Tailwind’s customization options go beyond traditional CSS frameworks, enabling you to craft consistent yet distinctive design systems. This means you can develop unique designs without sacrificing the efficiency of a utility-first workflow.

Next Steps to Optimize Your UI Components

To elevate your UI component system, consider these practical steps:

  • Build isolated base components for common elements like buttons, cards, and inputs.
  • Standardize component structures and naming conventions with detailed style guides.
  • Prioritize reusable components over relying solely on shared utility classes.
  • Use design tokens through your Tailwind configuration.
  • Automate class sorting with Prettier and Tailwind plugins.

Additionally, integrate with design systems to bridge the gap between designers and developers. These steps lay the groundwork for scalable, maintainable UI systems.

“In 2025, scalable frontend architecture isn’t a luxury - it’s a necessity. And the smartest dev teams aren’t just using components - they’re building design systems.”

  • Adnan Hamisi

Research shows that 80% of developers find utility-first frameworks easy to customize, which significantly enhances productivity. Tools like create-next-app already come preconfigured with Tailwind CSS, and its inclusion in job listings highlights its growing demand. Mastering these tools is a smart investment for your career and projects.

To further optimize performance, enable Tailwind’s JIT mode and style purging. Ensure accessibility by managing focus states, using ARIA attributes, and sticking to semantic HTML.

FAQs

How does using component abstraction with React and Tailwind CSS benefit the development process?

Using React and Tailwind CSS together makes building user interfaces a lot smoother. By focusing on component abstraction, developers can create reusable, consistent, and easy-to-maintain UI elements. This approach not only saves time but also ensures that designs stay uniform across different projects, cutting down on repetitive work.

With components that combine both styling and behavior, making updates becomes a breeze. Changes are faster, more efficient, and help keep technical debt in check. Tailwind CSS adds even more value with its utility-first styling. It lets developers apply precise styles directly in the component’s structure, speeding up the process and ensuring the design system stays organized and scalable.

What are the best practices for creating organized and consistent UI components with React and Tailwind CSS?

To craft organized and consistent UI components using React and Tailwind CSS, the first step is to establish a design system. This means setting up standardized elements like colors, typography, and spacing to ensure every component aligns with a unified style. Not only does this make your interface look consistent, but it also makes teamwork smoother and future updates more manageable.

Focus on breaking components into small, reusable pieces that can adapt to various states and scenarios through props. This modular structure minimizes repetitive code and simplifies maintenance. On top of that, techniques like lazy loading can boost performance by ensuring components are only loaded when they’re actually needed. Combining these approaches helps you build UI systems that are scalable, easy to maintain, and efficient.

How can you use Tailwind CSS to build responsive and reusable components in a React app?

To build flexible and reusable components in a React app with Tailwind CSS, you can make the most of its utility-first design. Tailwind’s utility classes let you apply styles directly in your JSX, speeding up your workflow and keeping everything consistent - no need for custom CSS files.

For handling responsiveness, Tailwind offers breakpoint-specific utility classes, which make it simple to adjust your components for various screen sizes. Additionally, the @apply directive allows you to create custom classes by grouping frequently used styles. This not only keeps your code cleaner but also makes it easier to manage in the long run.

By leveraging these tools, you can craft UI components that are efficient, adaptable, and ready to be reused throughout your project.

Share this post

Supercharge your web development workflow

Take your productivity to the next level, Today!

Written by
Author

Himanshu Mishra

Indie Maker and Founder @ UnveelWorks & Hoverify