Tailwind CSS Appearance
The appearance
property enables developers to control how native UI elements, such as form controls, are rendered by the browser. By default, many of these elements come with a platform-specific style. The appearance
property can be used to either preserve or remove these default styles.
Tailwind CSS abstracts this functionality through appearance-none
and appearance-auto
utilities. In this guide, we will learn how to work with these utilities in Tailwind CSS.
Class | Properties | Example |
---|---|---|
appearance-none | appearance: none; | <div className="appearance-none"></div> |
appearance-auto | appearance: auto; | <div className="appearance-auto"></div> |
Overview of Appearance
Adding the appearance-none
To reset the default browser styling of an element, use the appearance-none
utility. This is mostly used while creating form components.
export default function App() { return ( <div className="flex flex-col gap-4 justify-center items-center h-screen w-screen bg-gray-100"> <div> <label className="mr-8 text-sm underline">Default Style</label> <select> <option>Yes</option> <option>No</option> </select> </div> <div> <label className="mr-8 text-sm underline">Reset Style</label> <select class="appearance-none"> <option>Yes</option> <option>No</option> </select> </div> </div> ); }
States and Responsiveness
Hover and Focus States
Tailwind allows you to conditionally apply the appearance classes on certain states like hover and focus. Use Tailwind's state modifiers like- hover
, focus
, etc. to apply the utility only when these states are active, e.g., hover:appearance-none
.
export default function GradualAppearance() { return ( <div className="flex justify-center items-center h-screen w-screen bg-gray-100"> <div className="flex flex-col gap-4 justify-center items-center text-sm"> <label className="underline">Hover on the dropdown to reset style</label> <select class="hover:appearance-none"> <option>Yes</option> <option>No</option> </select> </div> </div> ); }
Breakpoint Modifiers
Tailwind CSS provides breakpoint modifiers to conditionally apply the appearance utility only when the screen hits the defined breakpoint. This is especially helpful for applying effects only on specific screens. Use Tailwind's breakpoint modifiers like- sm
, md
, etc., to apply the utility only on these breakpoints and above, e.g., sm:appearance-auto
.
export default function ResponsiveAppearance() { return ( <div className="flex justify-center items-center h-screen w-screen bg-gray-100"> <div className="flex flex-col gap-4 justify-center items-center text-sm"> <label className="underline">The dropdown style resets at <code>md</code> breakpoint</label> <select class="md:appearance-none"> <option>Yes</option> <option>No</option> </select> </div> </div> ); }
Real World Examples
Select Menu
A stylish dropdown menu component for a travel booking application with custom appearance styling.
const SelectMenu = () => { const destinations = [ { city: "Paris", country: "France", image: "https://images.unsplash.com/photo-1502602898657-3e91760cbb34", price: "€499", duration: "7 days", rating: 4.8 }, { city: "Tokyo", country: "Japan", image: "https://images.unsplash.com/photo-1503899036084-c55cdd92da26", price: "€799", duration: "10 days", rating: 4.9 }, { city: "New York", country: "USA", image: "https://images.unsplash.com/photo-1496442226666-8d4d0e62e6e9", price: "€649", duration: "5 days", rating: 4.7 }, { city: "Sydney", country: "Australia", image: "https://images.unsplash.com/photo-1506973035872-a4ec16b8e8d9", price: "€899", duration: "12 days", rating: 4.6 }, { city: "Dubai", country: "UAE", image: "https://images.unsplash.com/photo-1512453979798-5ea266f8880c", price: "€749", duration: "8 days", rating: 4.8 }, { city: "Rome", country: "Italy", image: "https://images.unsplash.com/photo-1529260830199-42c24126f198", price: "€549", duration: "6 days", rating: 4.7 } ]; return ( <div className="max-w-sm mx-auto p-4 bg-white rounded-lg shadow"> <div className="relative"> <select className="appearance-none w-full bg-white border border-gray-300 p-3 pr-8 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> <option>Select your destination</option> {destinations.map((dest, index) => ( <option key={index} value={dest.city}> {dest.city}, {dest.country} </option> ))} </select> <div className="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none"> <svg className="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20"> <path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" /> </svg> </div> </div> <div className="mt-4 space-y-3"> {destinations.map((dest, index) => ( <div key={index} className="flex items-center space-x-3 p-2 hover:bg-gray-50 rounded-lg cursor-pointer"> <img src={dest.image} alt={`${dest.city} thumbnail`} className="w-16 h-16 rounded-lg object-cover" /> <div className="flex-1"> <h3 className="text-sm font-medium text-gray-900">{dest.city}</h3> <p className="text-xs text-gray-500">{dest.country}</p> <div className="flex items-center mt-1"> <span className="text-xs font-medium text-blue-600">{dest.price}</span> <span className="mx-2 text-gray-300">•</span> <span className="text-xs text-gray-500">{dest.duration}</span> <span className="mx-2 text-gray-300">•</span> <div className="flex items-center"> <svg className="w-3 h-3 text-yellow-400" fill="currentColor" viewBox="0 0 20 20"> <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" /> </svg> <span className="ml-1 text-xs text-gray-500">{dest.rating}</span> </div> </div> </div> </div> ))} </div> </div> ); }; export default SelectMenu;
File Upload
A modern file upload component with drag and drop functionality and custom-styled input elements.
const FileUpload = () => { const files = [ { name: "project-proposal.pdf", size: "2.4 MB", type: "PDF", uploadDate: "Just now", icon: "https://images.unsplash.com/photo-1496171367470-9ed9a91ea931", status: "uploading" }, { name: "team-photo.jpg", size: "4.8 MB", type: "Image", uploadDate: "2 mins ago", icon: "https://images.unsplash.com/photo-1522202176988-66273c2fd55f", status: "complete" }, { name: "presentation.pptx", size: "8.1 MB", type: "Presentation", uploadDate: "5 mins ago", icon: "https://images.unsplash.com/photo-1542626991-cbc4e32524cc", status: "complete" }, { name: "budget.xlsx", size: "1.2 MB", type: "Spreadsheet", uploadDate: "10 mins ago", icon: "https://images.unsplash.com/photo-1554224155-8d04cb21cd6c", status: "complete" }, { name: "meeting-notes.docx", size: "856 KB", type: "Document", uploadDate: "15 mins ago", icon: "https://images.unsplash.com/photo-1618477388954-7852f32655ec", status: "complete" }, { name: "marketing-video.mp4", size: "15.7 MB", type: "Video", uploadDate: "20 mins ago", icon: "https://images.unsplash.com/photo-1536240478700-b869070f9279", status: "error" } ]; return ( <div className="max-w-sm mx-auto p-4 bg-white rounded-lg shadow"> <div className="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center"> <input type="file" className="appearance-none hidden" id="file-upload" multiple /> <label htmlFor="file-upload" className="cursor-pointer" > <svg className="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48"> <path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> </svg> <p className="mt-1 text-sm text-gray-600"> Drop files here or click to upload </p> <p className="mt-1 text-xs text-gray-500"> PDF, DOC, DOCX, JPG up to 10MB </p> </label> </div> <div className="mt-6 space-y-3"> {files.map((file, index) => ( <div key={index} className="flex items-center space-x-3 p-2 bg-gray-50 rounded-lg"> <div className="flex-shrink-0"> <img src={file.icon} alt="" className="w-10 h-10 rounded object-cover" /> </div> <div className="flex-1 min-w-0"> <p className="text-sm font-medium text-gray-900 truncate"> {file.name} </p> <p className="text-xs text-gray-500"> {file.size} • {file.uploadDate} </p> </div> <div className="flex-shrink-0"> {file.status === 'uploading' && ( <div className="h-1 w-24 bg-gray-200 rounded-full overflow-hidden"> <div className="h-1 bg-blue-500 w-1/2 rounded-full"></div> </div> )} {file.status === 'complete' && ( <svg className="w-5 h-5 text-green-500" fill="currentColor" viewBox="0 0 20 20"> <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /> </svg> )} {file.status === 'error' && ( <svg className="w-5 h-5 text-red-500" fill="currentColor" viewBox="0 0 20 20"> <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" /> </svg> )} </div> </div> ))} </div> </div> ); }; export default FileUpload;
Form Fields
A set of beautifully styled form inputs with custom appearance modifications for a job application form.
const FormFields = () => { const jobCategories = [ { id: 1, title: "Software Engineering", description: "Backend, Frontend, Full-stack positions", icon: "https://images.unsplash.com/photo-1461749280684-dccba630e2f6", positions: 12 }, { id: 2, title: "Product Design", description: "UI/UX, Visual Design, Product Design", icon: "https://images.unsplash.com/photo-1513542789411-b6a5d4f31634", positions: 8 }, { id: 3, title: "Data Science", description: "ML Engineers, Data Analysts, Scientists", icon: "https://images.unsplash.com/photo-1551288049-bebda4e38f71", positions: 5 }, { id: 4, title: "Marketing", description: "Digital Marketing, Content, SEO", icon: "https://images.unsplash.com/photo-1460925895917-afdab827c52f", positions: 7 }, { id: 5, title: "Operations", description: "Project Management, Administration", icon: "https://images.unsplash.com/photo-1454165804606-c3d57bc86b40", positions: 4 }, { id: 6, title: "Sales", description: "Account Executives, Sales Representatives", icon: "https://images.unsplash.com/photo-1556745757-8d76bdb6984b", positions: 6 } ]; return ( <div className="max-w-sm mx-auto p-4 bg-white rounded-lg shadow"> <form className="space-y-6"> <div> <label className="block text-sm font-medium text-gray-700 mb-1"> Your Name </label> <input type="text" className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" placeholder="John Doe" /> </div> <div> <label className="block text-sm font-medium text-gray-700 mb-1"> Job Category </label> <select className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"> <option value="">Select a category</option> {jobCategories.map((category) => ( <option key={category.id} value={category.id}> {category.title} ({category.positions} positions) </option> ))} </select> </div> <div className="space-y-3"> {jobCategories.map((category) => ( <label key={category.id} className="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer" > <input type="radio" name="job-category" value={category.id} className="appearance-none h-4 w-4 border border-gray-300 rounded-full checked:bg-blue-600 checked:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500" /> <div className="ml-3 flex items-center"> <img src={category.icon} alt="" className="w-10 h-10 rounded object-cover" /> <div className="ml-3"> <p className="text-sm font-medium text-gray-900"> {category.title} </p> <p className="text-xs text-gray-500"> {category.description} </p> </div> <span className="ml-auto text-xs text-blue-600 font-medium"> {category.positions} open </span> </div> </label> ))} </div> <button type="submit" className="appearance-none w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" > Apply Now </button> </form> </div> ); }; export default FormFields;
Video Player
A sleek video player interface with custom controls and appearance modifications.
const VideoPlayer = () => { const videos = [ { title: "Introduction to Web Development", duration: "15:30", thumbnail: "https://images.unsplash.com/photo-1498050108023-c5249f4df085", author: "Code Masters", views: "2.3k", progress: 100 }, { title: "JavaScript Fundamentals", duration: "22:15", thumbnail: "https://images.unsplash.com/photo-1579468118864-1b9ea3c0db4a", author: "JS Wizards", views: "1.8k", progress: 45 }, { title: "Responsive Design Basics", duration: "18:45", thumbnail: "https://images.unsplash.com/photo-1517180102446-f3ece451e9d8", author: "Design Pros", views: "3.1k", progress: 0 }, { title: "CSS Grid Layout", duration: "20:10", thumbnail: "https://images.unsplash.com/photo-1587620962725-abab7fe55159", author: "CSS Ninjas", views: "1.5k", progress: 0 }, { title: "React Hooks Deep Dive", duration: "25:30", thumbnail: "https://images.unsplash.com/photo-1633356122544-f134324a6cee", author: "React Masters", views: "4.2k", progress: 0 }, { title: "API Integration Guide", duration: "28:15", thumbnail: "https://images.unsplash.com/photo-1516259762381-22954d7d3ad2", author: "Backend Pros", views: "2.7k", progress: 0 } ]; return ( <div className="max-w-sm mx-auto bg-gray-900 rounded-lg overflow-hidden"> <div className="relative aspect-video bg-black"> <img src={videos[0].thumbnail} alt="Video thumbnail" className="w-full h-full object-cover" /> <div className="absolute inset-0 flex items-center justify-center"> <button className="appearance-none bg-blue-600 rounded-full p-4 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-900"> <svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" /> </svg> </button> </div> <div className="absolute bottom-0 inset-x-0 bg-gradient-to-t from-black/60 to-transparent p-4"> <div className="space-y-2"> <div className="flex justify-between items-center"> <span className="text-sm text-white">12:45</span> <span className="text-sm text-white">15:30</span> </div> <div className="relative"> <div className="appearance-none h-1 w-full bg-gray-600 rounded-full overflow-hidden"> <div className="h-full w-3/4 bg-blue-500 rounded-full"></div> </div> <div className="absolute h-3 w-3 bg-blue-500 rounded-full -mt-1 cursor-pointer" style={{ left: '75%' }}></div> </div> <div className="flex justify-between items-center"> <div className="flex space-x-4"> <button className="appearance-none text-white hover:text-blue-500"> <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zM7.555 8.168A1 1 0 008 9v2a1 1 0 001.555.832L12 10.5 9.555 9.168A1 1 0 008 9z" /> </svg> </button> <button className="appearance-none text-white hover:text-blue-500"> <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <path d="M15.555 8.168A1 1 0 0116 9v2a1 1 0 01-1.555.832L12 10.5l2.445-1.332A1 1 0 0116 9z" /> </svg> </button> </div> <div className="flex space-x-4"> <button className="appearance-none text-white hover:text-blue-500"> <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <path d="M10 12a2 2 0 100-4 2 2 0 000 4z" /> <path fillRule="evenodd" d="M3 5a2 2 0 012-2h10a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V5zm12 0H5v10h10V5z" clipRule="evenodd" /> </svg> </button> <button className="appearance-none text-white hover:text-blue-500"> <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <path d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" /> </svg> </button> </div> </div> </div> </div> </div> <div className="p-4 space-y-4"> {videos.map((video, index) => ( <div key={index} className="flex space-x-3 cursor-pointer hover:bg-gray-800 p-2 rounded"> <div className="relative flex-shrink-0"> <img src={video.thumbnail} alt={video.title} className="w-24 h-16 object-cover rounded" /> <span className="absolute bottom-1 right-1 text-xs text-white bg-black/60 px-1 rounded"> {video.duration} </span> {video.progress > 0 && ( <div className="absolute bottom-0 left-0 right-0 h-1 bg-gray-600"> <div className="h-full bg-blue-500" style={{ width: `${video.progress}%` }} ></div> </div> )} </div> <div className="flex-1 min-w-0"> <p className="text-sm font-medium text-white truncate"> {video.title} </p> <p className="text-xs text-gray-400 mt-1"> {video.author} </p> <p className="text-xs text-gray-500"> {video.views} views </p> </div> </div> ))} </div> </div> ); }; export default VideoPlayer;
Rating Input
A modern rating system with customizable emojis and detailed feedback form, perfect for product reviews or feedback collection.
const RatingInput = () => { const products = [ { id: 1, name: "Wireless Noise-Canceling Headphones", brand: "SoundMaster", price: "$299", image: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e", rating: 4.8, reviews: 427, purchaseDate: "2024-01-15" }, { id: 2, name: "Smart Fitness Watch Pro", brand: "TechFit", price: "$199", image: "https://images.unsplash.com/photo-1523275335684-37898b6baf30", rating: 4.5, reviews: 312, purchaseDate: "2024-02-01" }, { id: 3, name: "Ergonomic Office Chair", brand: "ComfortPlus", price: "$249", image: "https://images.unsplash.com/photo-1505843490578-d3ed6809a1e7", rating: 4.6, reviews: 189, purchaseDate: "2024-01-28" }, { id: 4, name: "Professional Camera Lens", brand: "OptiMax", price: "$799", image: "https://images.unsplash.com/photo-1515343480029-43cdfe6b6aae", rating: 4.9, reviews: 156, purchaseDate: "2024-02-10" }, { id: 5, name: "Smart Home Security System", brand: "SafeGuard", price: "$399", image: "https://images.unsplash.com/photo-1558002038-1055907df827", rating: 4.7, reviews: 234, purchaseDate: "2024-01-20" }, { id: 6, name: "Portable Power Bank", brand: "PowerTech", price: "$49", image: "https://images.unsplash.com/photo-1593510987046-1f8fcfc84d14", rating: 4.4, reviews: 512, purchaseDate: "2024-02-05" } ]; const emojis = [ { rating: 1, emoji: "😠", label: "Terrible" }, { rating: 2, emoji: "😕", label: "Poor" }, { rating: 3, emoji: "😐", label: "Okay" }, { rating: 4, emoji: "😊", label: "Good" }, { rating: 5, emoji: "😍", label: "Excellent" } ]; return ( <div className="max-w-sm mx-auto bg-white rounded-lg shadow"> <div className="p-4 space-y-4"> <div className="space-y-2"> <h3 className="text-sm font-medium text-gray-900">Rate your recent purchases</h3> <p className="text-xs text-gray-500">Your feedback helps improve our products</p> </div> <div className="space-y-4"> {products.map((product) => ( <div key={product.id} className="border rounded-lg p-3 space-y-3"> <div className="flex items-center space-x-3"> <img src={product.image} alt={product.name} className="w-16 h-16 rounded-lg object-cover" /> <div className="flex-1 min-w-0"> <p className="text-sm font-medium text-gray-900 truncate"> {product.name} </p> <p className="text-xs text-gray-500"> {product.brand} • Purchased {product.purchaseDate} </p> <div className="flex items-center mt-1"> <div className="flex"> {[1, 2, 3, 4, 5].map((star) => ( <svg key={star} className={`w-3 h-3 ${ star <= product.rating ? "text-yellow-400" : "text-gray-300" }`} fill="currentColor" viewBox="0 0 20 20" > <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" /> </svg> ))} </div> <span className="ml-1 text-xs text-gray-500"> ({product.reviews}) </span> </div> </div> <div className="flex-shrink-0"> <span className="text-sm font-medium text-gray-900"> {product.price} </span> </div> </div> <div className="border-t pt-3"> <div className="flex justify-between items-center"> {emojis.map((emoji) => ( <label key={emoji.rating} className="flex flex-col items-center cursor-pointer group" > <input type="radio" name={`rating-${product.id}`} value={emoji.rating} className="appearance-none absolute" /> <span className="text-2xl group-hover:transform group-hover:scale-125 transition-transform"> {emoji.emoji} </span> <span className="mt-1 text-xs text-gray-500"> {emoji.label} </span> </label> ))} </div> </div> <div className="border-t pt-3"> <textarea rows="2" className="appearance-none block w-full text-xs border border-gray-200 rounded-md p-2 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="Share your thoughts about this product (optional)" ></textarea> <div className="mt-2 flex justify-end"> <button className="appearance-none text-xs text-white bg-blue-600 px-3 py-1.5 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"> Submit Review </button> </div> </div> </div> ))} </div> </div> </div> ); }; export default RatingInput;