Thinking Headless UI
Components that have no interface…
The Philosophy of Headless
Web components are made of two things, interface and logic to drive the interface.
Separating logic from the interface is the core philosophy of headless.
It allows us to build highly flexible, adaptive web components that can work across UI libraries.
Example — A standalone toggle button
We will be making use of React Hooks to build the toggle button.
To build the button, what we need is a status to indicate whether the button is pressed or not and an event handler to toggle the status on click.
Types Definition
type ButtonPropsGetter = () => {
onClick: (e: MouseEvent<HTMLButtonElement>) => void;
"aria-pressed": "true" | "false";
};
type StateGetter = () => boolean;
type ToggleButtonGetters = {
getButtonProps: ButtonPropsGetter;
getState: StateGetter;
};
useToggleButton hook
const useToggleButton = (): ToggleButtonGetters => {
const [status, setStatus] = useState(false);
const onClick = () => {
setStatus((prev) => !prev);
};
const getButtonProps: ButtonPropsGetter = () => {
return { onClick, "aria-pressed": status ? "true" : "false" };
};
const getState: StateGetter = () => status;
return { getButtonProps, getState };
};
That’s it! we can easily consume this, be it native button or any other react library
Consumption
import useToggleButton from "./useToggleButton";
const App: FC = () => {
const { getButtonProps, getState } = useToggleButton();
const status = getState();
return (
<div>
<h1>Hello World!</h1>
{/* We can replace this button with any button */}
<button {...getButtonProps()}>{status ? "ON" : "OFF"}</button>
</div>
);
}
We can easily modify the hook to accept an initial value without touching the rendering part OR We can easily modify the button without touching the hook part.
Popular headless libraries
Thanks.