Stop Mixing UI Components With Business Logic

Stop Mixing UI Components With Business Logic

# frontend# architecture# cleancode# softwareengineering
Stop Mixing UI Components With Business LogicAndré Luiz Lunelli

TL;DR Separate your frontend components into two clear groups: components/ │ ├── app/ →...

TL;DR

Separate your frontend components into two clear groups:

components/
│
├── app/ → // components that encapsulate business logic
│   ├── AppliesCoupons.vue
│   └── SearchUserInput.vue
│
└── ui/  → // purely visual components
    ├── Badge.vue
    ├── Button.vue
    └── Input.vue
Enter fullscreen mode Exit fullscreen mode

This fosters a clean architecture, and it works whether you're using Blade, Livewire, Vue, React, or anything else.

Read the Full Thing

At some point, I noticed something was bothering me in my projects.

I was already using components everywhere. Buttons were components. Inputs were components. Modals were components.
Don't get me wrong, that part was good.

But I often found myself doing things like building a registration page where I had an input field that checked whether the email already existed in the database.

So I would create something like:

RegisterEmailInput.vue
Enter fullscreen mode Exit fullscreen mode

Inside it, I'll add:

  • Debounce logic
  • An API call
  • Domain validation
  • Error interpretation

This would definitely work. But something felt off.

Was that really just an "input"? Or was it part of the application logic?

The Problem

When everything lives inside the same components/ folder, you start losing clarity.

A Button.vue and a SearchUserInput.vue don’t belong to the same category of responsibility.

One is visual. The other represents domain behavior.

That’s when I made a structural change to the structure I use now:

components/
│
├── app/
│   ├── AppliesCoupons.vue
│   └── SearchUserInput.vue
│
└── ui/
    ├── Badge.vue
    ├── Button.vue
    └── Input.vue
Enter fullscreen mode Exit fullscreen mode

ui/

  • Purely visual
  • Reusable anywhere
  • No API calls
  • No domain knowledge

You could copy them to another project and they would still work.

app/

  • Talks to APIs
  • Applies business rules
  • Knows about the domain
  • Controls behavior

These components are not just UI.
They represent application logic.

Why This Matters

This separation changes how you think.

Instead of asking:

“Where do I put this component?”

You start asking:

“Is this visual, or does it represent domain behavior?”

That question alone improves architectural clarity.

It also makes:

  • Refactoring easier
  • Testing more predictable
  • Responsibilities clearer
  • Code reviews more objective
  • The codebase easier to navigate
  • Feature responsibilities easier to understand

And the best part?

This approach works in: Laravel (Blade / Livewire), Vue, React or any other component-based frontend. Because this isn’t about framework.