Jason Beggs

Engineer at Laravel

Tips for good UI implementation

This article covers tips and techniques I've learned while implementing designs over several years. It will likely change over time as I pick up more techniques and my opinions change. Don't apply these tips blindly. Instead, consider the motivation behind each one and decide whether it applies to your situation.

Fundamentals

Inspect the designs carefully

While it's easy to eyeball a design and get it "close enough," inspect the designs for details. The designer probably intentionally made that gap 8px instead of 10px.

Make a design system

When you first get handed a design, if there’s no obvious design system, make one. Click around the design to identify the colors and fonts in use. Then set up your Tailwind config in an organized way.

Remove Tailwind’s default colors

Unless a design is using Tailwind’s colors, reset the color palette before adding custom colors. This prevents accidentally using a Tailwind color instead of the custom colors from the design.

Apply `antialiased` to the body tag

antialiased makes fonts render much smoother and has a significant effect on how font weights appear.

Use semantic markup

Use semantic elements (p, strong, blockquote, etc.) when appropriate instead of divs everywhere. Use a for links and navigation to preserve the browser tooltips and cmd+click to open in a new tab. Use button for all other types of interactivity.

Be selective with animations

Animations and transitions should be intentional and purposeful. If animations are overdone or not done well, they can feel distracting or make an app feel slow.

Emil Kowalski wrote an excellent article about this called “You Don’t Need Animations”. It includes some concrete examples that demonstrate the idea very clearly.

Default state and classes on page load

When using tools like Alpine.js for elements like mobile menus, set the default state for components to closed. Use x-cloak to prevent momentary display of close buttons before Alpine initializes.

Interactive elements

Cursor-pointer

cursor-pointer should be used on links to indicate navigation to a different page. cursor-default should be used on all other interactive elements combined with obvious hover states to indicate interactivity.

Use default browser focus styles

Leave the default browser focus styles unless you're willing to ensure all elements have excellent focus styles in all contexts. They’re not the prettiest, but they do work in all situations and look better than inconsistent custom styles.

Use focus-visible

When implementing custom focus styles, prefer focus-visible over focus so there are no flashes of a focus state when clicking links and buttons. focus-visible will only show the focus state when the element is focused via keyboard.

Apply hover and focus states to the interactive element

Hover states and focus states should match the area that the user can interact with. If you need to style children of a button differently on hover, add a group to the button, then style the children using group-hover.

Images

Remove shadows from images before exporting them

Remove shadows from images (especially screenshots) before exporting them from design tools, then reapply the shadows in code. It’s much easier to make sure the images are sized correctly and stay aligned if the shadow is rendered separately from the image itself.

Remove border-radius from images before exporting them

Remove the border-radius from images before exporting so you can adjust the radius in code without re-exporting.

Remove masks and gradients from images before exporting them

Remove the masks and gradients from images that fade out near the edges before exporting them and implement a mask using Tailwind's mask utilities. Gradients implemented in CSS will be much smoother and can be responsive if necessary.

Use proper image formats, sizing, and optimization

Use SVGs for illustrations and icons. SVGs scale up and down without getting blurry.

Raster images (JPGs, PNGs) should always be resized to exactly 2x their rendered size. If the image is rendered at 512px, it should be 1024px. Otherwise, it will appear blurry on Retina screens.

Optimize all images using SVGOMG, ImageOptim, or similar tools before committing them.

Use currentColor on SVGs

When SVG icons/illustrations are a single color, replace the stroke or fill with currentColor and implement the color using CSS text colors from the design system. This approach simplifies updates for dark mode or design system color changes.

Use loading="lazy" on images when appropriate

Use loading=”lazy” on images below the fold so they don’t slow down the initial page load.

Consistency

Apply consistent paddings and max-widths for alignment

Most designs have some sort of grid that things should align to. Apply max-widths and paddings consistently in the same places to ensure proper alignment. Make a container component if you can, but this isn’t always possible and the rule applies to more elements than just the container.

Use loops/extract components

If an element is repeated several times, use a loop or extract a component so the CSS classes aren’t repeated. This will help ensure inconsistencies aren’t introduced as styling changes over time.

Other helpful resources

Published on