
Custom Events and Component Communication in Vue - `emit`, `provide/inject`, and When to Use a Store
} -->
“Be formless, shapeless, like water.” - Bruce Lee
When designing UI components, one of the most underrated skills is knowing what your component should control, and what it should delegate.
Let’s talk about a subtle but powerful pattern:
components that define their own style, but let the parent define the layout.
I like to call them water-style components.
Imagine a Button
component.
You want it to look consistent across your app - rounded corners, colors, hover states, typography — that’s its visual style.
But you don’t want the Button
to decide:
inline
, flex-grow
, ml-4
, mt-8
, or w-full
That should be decided by the parent, not the button itself.
Just like water takes the shape of the glass, these components take the shape of their container - adapting to layout context, not dictating it.
Here’s how a Button
might look:
// Button.tsx
export function Button({ variant = "primary", children }) {
const base = "rounded px-4 py-2 font-semibold text-sm";
const variants = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
outline: "border border-gray-400 text-gray-800 bg-white",
};
return <button className={`${base} ${variants[variant]}`}>{children}</button>;
}
And then we use it like this:
<div className="flex justify-end mt-8 gap-2">
<Button variant="outline">Cancel</Button>
<Button variant="primary">Save</Button>
</div>
The button defines:
Colors
Padding
Rounded corners
Font weight & size
But the parent defines:
Margin
Flexbox layout
Spacing between buttons
Placement on the page
This approach gives you:
Reusability - use the same component in different contexts
Design consistency - style is centralized, not scattered
Layout flexibility - change positioning without modifying the component
Predictability - each layer has clear responsibility
It also plays perfectly with:
Tailwind CSS (utility-first styling)
Variant props (for themes or states)
Design systems with composable primitives
Don’t do this inside the component:
return <button className="w-full mx-auto my-10 text-center bg-blue-600">...</button>;
This mixes layout and style - making the button rigid, harder to reuse, and harder to maintain.
In frameworks like Vue or Svelte, this idea works beautifully with slot-based composition.
You can build layout-agnostic components that wrap any content and still adapt to layout externally.
Start thinking of your components like water:
Style yourself, but don’t control the environment.
Provide your own look and feel.
Let the parent decide the placement, size, and flow.
This one mindset shift can make your component library cleaner, more reusable, and more fun to work with.
Bartłomiej Nowak
Programmer
Programmer focused on performance, simplicity, and good architecture. I enjoy working with modern JavaScript, TypeScript, and backend logic — building tools that scale and make sense.