Learn Remix - UI & Component Composition
Episode 6 of 24

Learn Remix - UI & Component Composition

This episode covers the visual side of Remix: creating reusable React components, styling with CSS Modules, Tailwind, and styled-components, client-side interactivity, and how to optimize UI that's rendered on the server.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Routing and data are under your belt. Now we get into the most visible part: UI and component composition. This is where technical code turns into the experience users feel.

Remix is React, so all of your React component habits apply. What's different is discipline: because components are rendered on the server and then hydrated on the client, you must be aware of which code runs on the server, on the client, or both. This episode teaches that habit from the start.

Episode 6 covers reusable components, three styling approaches common in the Remix ecosystem, client-side interactivity, and how to optimize UI that's born on the server.

Reusable React Components

Components as Functions

Components are functions that receive props and return UI elements. The key to reuse is separating repeated parts into small, single-responsibility components — like a button, card, or badge — then composing them in pages.

JSReusable component with props
export function Kartu({ judul, children }) {
  return (
    <article>
      <h2>{judul}</h2>
      <div>{children}</div>
    </article>
  );
}

Props determine component behavior; children enable nested composition. With this pattern, pages simply assemble blocks instead of repeating the same markup over and over.

Component Folder

Store components alongside the routes that use them, or in the app/components folder when used across pages. This pattern is covered in more depth in episode 18. For now, just remember the principle: one component, one file, one responsibility.

Styling in Remix

CSS Modules

Remix supports CSS Modules with no extra configuration. Create a .module.css file, import it in a component, and Remix handles its scoping. Classes are scoped per component so there are no name collisions across files.

CSSButton.module.css file
.tombol { background: #2563eb; color: white; padding: 8px 16px; }
JSUsing CSS Modules
import styles from "./Button.module.css";
 
export function Tombol() {
  return <button className={styles.tombol}>Klik</button>;
}

Importing styles from a .module.css file produces an object that maps class names. Remix even optimizes CSS by only sending the files used by a given route.

Tailwind CSS

Tailwind is the most popular choice in the Remix community. The integration is simple: install the Tailwind packages, run init, and point the content path at the app folder. Notably, in Vite-based Remix v3, the Tailwind plugin can be installed directly in vite.config.ts.

styled-components

styled-components and other CSS-in-JS libraries also work, but there's a cost: new CSS is injected after hydration, which can cause a flash. If you choose CSS-in-JS, consider an approach that supports server rendering. For content-based applications, CSS Modules and Tailwind are generally a better fit.

Client-Side Interactivity

Event Handlers and State

Even though it's rendered on the server, a component becomes full React after hydration. Event handlers like onClick and state hooks like useState work normally on the client. Purely local interactivity — toggles, tabs, accordions — doesn't need a loader or action.

JSLocal interactivity with useState
import { useState } from "react";
 
export default function Akordeon({ judul, isi }) {
  const [terbuka, setTerbuka] = useState(false);
  return (
    <div>
      <button onClick={() => setTerbuka(!terbuka)}>{judul}</button>
      {terbuka ? <p>{isi}</p> : null}
    </div>
  );
}

Local state like useState only exists on the client after hydration. UI that needs server data still uses loaders; purely visual interactions only need local state.

When to Use Client Rendering

Use client rendering for parts that need real-time behavior: chat, timers, drag and drop. For everything else, let the server render. This principle keeps pages fast because the JavaScript sent is only for the interactions that actually exist.

Optimizing Server-Rendered UI

Reducing the Client Load

Because Remix sends HTML that already contains content, first paint is very fast. The next main optimization is sending as little JavaScript as possible. Avoid writing manual fetch in components — that's the loader's job. Use Link prefetch to prepare the next page's data on hover.

JSPrefetch data when a link is hovered
import { Link } from "@remix-run/react";
 
export default function DaftarArtikel() {
  return <Link prefetch="intent" to="/artikel/1">Baca artikel</Link>;
}

The prefetch="intent" prop asks Remix to load the route's data when the cursor approaches the link. Clicking feels instant because the data is already cached on the client.

Auditing with Lighthouse

Finally, make a habit of checking your work with Lighthouse in the browser. Measure the time until the first content is visible, the amount of JavaScript sent, and the accessibility score. Episode 9 covers performance optimization in more depth, including caching.

Conclusion

Episode 6 closes the UI triangle: well-organized reusable components, styling with CSS Modules, Tailwind, or styled-components, client-side interactivity that's hydration-aware, and optimization so server-rendered UI stays light on the client.

The key takeaways:

  • Components are functions that receive props; use children for composition.
  • CSS Modules work with no configuration; Tailwind is installed via the Vite plugin.
  • CSS-in-JS needs special attention to avoid flash on server render.
  • Local interactivity only needs React state; data still goes through loaders.
  • prefetch="intent" prepares data before a link is clicked.
  • Audit regularly with Lighthouse to monitor JavaScript weight and accessibility.

In the next episode, episode 7, we'll discuss forms and validation — form handling with Remix's Form component, server-side validation with client feedback, rendering per-field errors, and progressive enhancement so forms keep working without JavaScript. Forms are where Remix truly shines.

Learn Remix - UI & Component Composition | Learn Remix