November 2026

How to Add a Custom Favicon and App Icons to a Website (HTML Code for Every Device)

Adding a favicon should take two minutes, yet most tutorials still hand you a wall of fifteen <link> tags copied from 2014. Half of those tags target browsers that no longer exist. This guide gives you one copy-paste favicon HTML code snippet, a table of the file sizes that are actually required in 2026, and the Express configuration to serve them correctly. The short answer: the favicon HTML code you need Place this inside the <head> of every page (or in your layout template). Four lines, four files, every modern browser and device covered. <!– Classic favicon for browser tabs and bookmarks –> <link rel="icon" href="/favicon.ico" sizes="32×32"> <!– Modern scalable icon (supports dark mode) –> <link rel="icon" href="/icon.svg" type="image/svg+xml"> <!– iOS / iPadOS home screen –> <link rel="apple-touch-icon" href="/apple-touch-icon.png"> <!– Android, Chrome, PWA install prompts –> <link rel="manifest" href="/manifest.webmanifest"> Three things to check before you move on: The tags must be inside <head>, not <body>. Use absolute paths starting with a slash (/favicon.ico), not favicon.ico. Relative paths break on nested URLs such as /blog/post/. The four files live at the root of your public directory, so they resolve at https://example.com/favicon.ico. What each line actually does 1. favicon.ico The ICO file is still the most reliable fallback. Browsers request /favicon.ico automatically even if you never declare it, and some crawlers, RSS readers, feed aggregators and email clients only look for that file. Keep it as a multi-resolution ICO containing 16×16, 32×32 and 48×48 layers. The sizes="32×32" hint tells Chrome and Firefox which layer to prefer, which avoids blurry upscaled tabs on high-DPI screens. 2. icon.svg An SVG favicon scales perfectly on every pixel density and supports a dark mode variant inside the file itself. Chrome, Edge, Firefox, Opera and Safari all support it. Browsers that do not will silently fall back to the ICO, which is why both lines belong in your snippet. 3. apple-touch-icon.png This is the icon iOS uses when a visitor taps Add to Home Screen. One 180×180 PNG is enough. iOS downscales it for smaller devices. Do not add transparency: iOS fills transparent pixels with black. Use a solid background and keep the corners square, because the system applies its own rounding. 4. manifest.webmanifest Android and Chromium-based browsers read the manifest instead of <link> tags for home screen and install icons. This is also what makes your site installable as a PWA. Favicon sizes: the only files you need in 2026 File Size Format Used by Required? favicon.ico 16×16, 32×32, 48×48 in one file ICO Browser tabs, bookmarks, history, crawlers, feed readers Yes icon.svg Any (vector), design on a 32×32 grid SVG All modern desktop and mobile browsers, dark mode Recommended apple-touch-icon.png 180×180 PNG (no alpha) iOS and iPadOS home screen, Safari shared links Yes icon-192.png 192×192 PNG Android home screen, manifest Yes icon-512.png 512×512 PNG PWA splash screens, app listings, install dialog Yes icon-maskable-512.png 512×512 with 20% safe padding PNG Android adaptive icons (circle, squircle, teardrop masks) Optional but strongly advised for PWAs That is six files maximum, and realistically four if you are not shipping a PWA. Anything beyond this list is legacy baggage. The web app manifest Create manifest.webmanifest at the root of your public folder: { "name": "Your Site Name", "short_name": "YourSite", "start_url": "/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#0b5fff", "icons": [ { "src": "/icon-192.png", "type": "image/png", "sizes": "192×192" }, { "src": "/icon-512.png", "type": "image/png", "sizes": "512×512" }, { "src": "/icon-maskable-512.png", "type": "image/png", "sizes": "512×512", "purpose": "maskable" } ] } Notes that save debugging time: The manifest must be served with the application/manifest+json MIME type. Express does this automatically for the .webmanifest extension in recent versions of mime-db, but check your response headers. If your manifest sits on another origin or you fetch it with credentials, add crossorigin="use-credentials" to the link tag. A maskable icon needs its artwork inside the central 80% circle, otherwise Android will crop your logo. Dark mode favicons the clean way You do not need extra HTML. Put a media query inside the SVG file itself, and the icon reacts to the operating system theme: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"> <style> circle { fill: #0b1220; } @media (prefers-color-scheme: dark) { circle { fill: #ffffff; } } </style> <circle cx="16" cy="16" r="14"/> </svg> Keep SVG favicons extremely simple. At 16 pixels, gradients, thin strokes and text turn into mush. One shape, one or two colors, high contrast. Tags you can delete in 2026 If your <head> contains any of the following, remove them. They add bytes and parsing work for zero benefit. Legacy tag or file Why it is obsolete rel="shortcut icon" Invalid keyword kept alive only for Internet Explorer. rel="icon" is the standard. rel="mask-icon" (Safari pinned tab SVG) Deprecated since Safari 12. Safari now uses your regular icons. msapplication-TileImage, browserconfig.xml Windows 8 and 10 live tiles no longer exist. The 10 apple-touch-icon sizes (57, 60, 72, 76, 114, 120, 144, 152 and so on) iOS downscales a single 180×180 PNG perfectly. apple-touch-icon-precomposed iOS stopped applying gloss effects years ago. android-chrome-192×192.png declared with a link tag Android reads the manifest, not link tags. Serving your favicon correctly with Express Favicon problems are very often server problems, not HTML problems. In an Express app, put the icon files in your static directory root: const express = require(‘express’); const path = require(‘path’); const app = express(); app.use(express.static(path.join(__dirname, ‘public’), { maxAge: ’30d’, etag: true })); app.listen(3000); With public/favicon.ico, public/icon.svg, public/apple-touch-icon.png, public/icon-192.png, public/icon-512.png and public/manifest.webmanifest in place, every path in the snippet resolves. (via https://coddy.tech) Avoiding the 404 flood in your logs Browsers request /favicon.ico on their own. If the file is missing, your logs fill with 404 lines and your error handler may be triggered on every page view. Two options: Ship the file. The simplest and best fix. Return a 204 explicitly while you are still designing the icon: app.get(‘/favicon.ico’, (req, res) => res.status(204).end()); Register that route before your catch-all handlers so it is not swallowed by a wildcard route. Caching strategy Give favicon.ico and icon.svg a moderate cache (a

How to Add a Custom Favicon and App Icons to a Website (HTML Code for Every Device) Read More »

How to Create a Custom WordPress Login Page Without a Plugin (Code Guide)

Every agency hits the same moment: the site is beautiful, the dashboard is clean, and then the client logs in and sees a plain blue-grey screen with the WordPress logo on top. If you want a genuinely white-labeled handover, you need to customize the WordPress login page itself. Most tutorials answer that with “install a login customizer plugin”. That works, but it also means one more plugin to update, one more vendor to trust, and one more thing that can break on a client site you maintain. This guide takes the other route: a few well-chosen hooks and a single CSS file, deployed the way a professional team would deploy it. The reasoning is set out in this piece. What you will build By the end of this tutorial, the wp-login.php screen for your client will have: The client logo instead of the WordPress logo, linking to the client’s homepage A branded background (image, gradient or solid colour) Restyled form fields, labels and submit button matching the brand A custom welcome message above the form Generic error messages that do not leak whether a username exists A role-based redirect after login (editors to Posts, clients to the dashboard, etc.) A discreet agency credit in the footer, if you want one Total: roughly 60 lines of PHP and 70 lines of CSS. No plugin. Before you start: never edit wp-login.php This is the single most important rule. The file wp-login.php lives in the WordPress core directory and is overwritten on every core update. Any change you make there disappears silently, usually at the worst possible moment. WordPress exposes a full set of hooks specifically so you never have to touch it. Use those instead. Where should the code live? Location Best for Survives theme switch? Child theme functions.php One-off sites, branding tied to the theme No Custom plugin in /wp-content/plugins/ Reusable agency boilerplate Yes (can be deactivated) Must-use plugin in /wp-content/mu-plugins/ White-label work the client should not switch off Yes (cannot be deactivated from admin) Our recommendation for agency work: the mu-plugin. The branding stays in place even if the client swaps the theme or a junior dev deactivates something, and it keeps the login logic separate from presentation code. Create /wp-content/mu-plugins/client-login-branding.php and start with this header: <?php /** * Plugin Name: Client White Label Login * Description: Rebrands the wp-login.php screen for this client. * Version: 1.0.0 * Author: Your Agency */ if ( ! defined( ‘ABSPATH’ ) ) { exit; } If you prefer the child theme route, drop everything below into functions.php instead and swap plugin_dir_url( __FILE__ ) for get_stylesheet_directory_uri(). Step 1: Load a dedicated login stylesheet The hook you need is login_enqueue_scripts. It fires only on the login, registration and lost password screens, so your CSS never loads on the front end or in the admin. function agency_login_assets() { $css_path = __DIR__ . ‘/assets/login.css’; $css_url = plugin_dir_url( __FILE__ ) . ‘assets/login.css’; wp_enqueue_style( ‘agency-login’, $css_url, array( ‘login’ ), file_exists( $css_path ) ? filemtime( $css_path ) : ‘1.0.0’ ); } add_action( ‘login_enqueue_scripts’, ‘agency_login_assets’ ); Two details that separate sloppy code from clean code: array( ‘login’ ) declares a dependency on the core login stylesheet, so yours always loads after it and your rules win without !important spam. filemtime() gives you automatic cache busting. Edit the CSS, the version string changes, the browser and any CDN pick it up immediately. Prefer no extra HTTP request? For very small amounts of CSS, you can print it inline on the same hook: function agency_login_inline_css() { $logo = plugin_dir_url( __FILE__ ) . ‘assets/client-logo.svg’; ?> <style> .login h1 a { background-image: url(‘<?php echo esc_url( $logo ); ?>’); background-size: contain; width: 100%; height: 90px; } </style> <?php } add_action( ‘login_enqueue_scripts’, ‘agency_login_inline_css’ ); Always run dynamic URLs through esc_url(). It costs nothing and it is the kind of detail a code review will catch. Step 2: Replace the WordPress logo with the client logo The logo is an <h1> containing a link, and WordPress renders it as a background image. Target it like this in assets/login.css: .login h1 a { background-image: url(‘../assets/client-logo.svg’); background-size: contain; background-position: center center; background-repeat: no-repeat; width: 100%; height: 90px; margin-bottom: 24px; } Use an SVG or a 2x PNG. The login logo renders on retina screens and a 300px-wide JPEG will look amateurish on the one screen your client sees every single day. Fix the logo link and tooltip By default the logo links to wordpress.org with the title “Powered by WordPress”. That is the fastest way to blow your white-label cover. Two filters solve it: add_filter( ‘login_headerurl’, function () { return home_url( ‘/’ ); } ); add_filter( ‘login_headertext’, function () { return get_bloginfo( ‘name’ ); } ); Note: login_headertext replaced the old login_headertitle filter. If you are copying code from a 2016 tutorial, that is why your tooltip never changed. Step 3: Brand the background The login screen body carries the class .login, which makes full-screen backgrounds trivial: body.login { background: #0f172a url(‘../assets/login-bg.jpg’) no-repeat center center fixed; background-size: cover; } /* Gradient alternative, no image weight */ body.login { background: linear-gradient(135deg, #0f172a 0%, #2563eb 100%); } Keep the hero image under 250 KB and serve WebP where you can. The login page is not the place to ship a 3 MB photograph. Add a per-client body class If you maintain a multisite or a shared boilerplate, the login_body_class filter lets one stylesheet handle many brands: add_filter( ‘login_body_class’, function ( $classes ) { $classes[] = ‘brand-acme’; return $classes; } ); Step 4: Style the form, fields and button This is where the page stops looking like “WordPress with a logo swapped” and starts looking like a product. Here is a solid base to adapt: #login { width: 400px; padding: 6% 0 0; } .login form { border: 0; border-radius: 14px; padding: 32px 28px 26px; box-shadow: 0 24px 50px rgba(15, 23, 42, .28); } .login label { color: #334155; font-size: 14px; font-weight: 600; } .login input[type=’text’], .login input[type=’password’], .login input[type=’email’] { border: 1px solid #cbd5e1; border-radius: 8px; padding: 10px 12px; font-size: 15px;

How to Create a Custom WordPress Login Page Without a Plugin (Code Guide) Read More »

How to Create an Interactive Map for a Website With Leaflet (No Google Maps API Key)

Short answer: you can build a custom interactive map for a website in roughly 20 lines of code using Leaflet.js and OpenStreetMap tiles. No Google Maps API key, no billing account, no credit card on file, and no third party tracking cookies loaded on your pages. There is more on it in Free Map Maker & Generator. Most articles on this topic push you toward a hosted map builder or toward the Google Maps Platform, where you have to enable billing before a single tile renders. That is fine for some projects, but it is overkill when all you need is a map of your offices, your store locations, your delivery zones or your event venues. This tutorial shows the developer route: a lightweight open source library, open data tiles, and full control over the markup. What You Will Build A full width, responsive map block that works on mobile and desktop Custom branded markers (your own PNG or SVG pin) Clickable popups with a title, address and link Multiple locations loaded from a JSON array or from an Express API route Auto zoom that fits every marker in the viewport A map that does not hijack page scrolling on mobile Total page weight: about 42 KB of JavaScript plus CSS, gzipped, versus several hundred KB for the typical commercial map SDK. Why Leaflet and OpenStreetMap Instead of Google Maps Leaflet is an open source mapping library (BSD licensed) that renders map tiles and vector overlays in the browser. OpenStreetMap is a community built, openly licensed map database. Together they give you a production grade map with zero account setup. Solution API key / billing Customisation Typical weight Privacy Leaflet + OpenStreetMap None Full (your HTML and CSS) ~42 KB JS No ad cookies Google Maps JS API Key + billing account required Good, via cloud styling 200 KB+ Consent banner usually needed Mapbox GL JS Key required, usage based pricing Excellent (vector styles) 200 KB+ Telemetry by default No code embeds (map builders) Account, often paid plan Limited to their editor Heavy iframe Third party scripts If you have a marketing team that needs to drag pins around without touching code, a hosted builder is a reasonable choice. If you own the codebase, Leaflet wins on speed, cost and control. The team at flourish.studio reached a similar conclusion. Before You Start A page you can edit (static HTML, a template, a CMS block or a React/Vue component) The latitude and longitude of each location. Right click a point on openstreetmap.org and choose Show address to read the coordinates, or geocode your addresses once with Nominatim and store the results. Optional: a marker image, ideally a 32×42 px PNG or an SVG Important: store your coordinates in your own database or JSON file. Geocoding on every page load is slow, and public geocoding services rate limit that behaviour. Step 1: Add the Leaflet CSS and JS Use the current stable 1.9.x line from a CDN, or install it locally with npm install leaflet if you bundle your assets. Self hosting the two files is the privacy friendly option and removes a third party request. <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"> <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script> The CSS must load before the map is initialised, otherwise tiles stack in a broken column. If you are serving files yourself, copy leaflet.css, leaflet.js and the images/ folder from the package into your public directory. Note on Leaflet 2.x Leaflet 2 moves to native ES modules and drops the global L object in favour of named imports. The concepts in this tutorial are identical, only the import syntax changes: import { Map, TileLayer, Marker } from 'leaflet'; If you are on a classic script tag setup, the 1.9.x line remains the safest pick today. Step 2: Create the Map Container and Give It a Height This is where 90 percent of blank map bugs come from. A div with no height renders nothing. <div id="map" role="region" aria-label="Map of our locations"></div> <style> #map { width: 100%; height: 60vh; min-height: 320px; border-radius: 12px; z-index: 0; } @media (max-width: 600px) { #map { height: 55vh; min-height: 280px; } } </style> Using vh with a min-height keeps the map usable on small screens without pushing the rest of the page below the fold. The z-index: 0 prevents Leaflet panes from floating above sticky headers and modals. Step 3: Initialise the Map and Load Tiles const map = L.map('map', { scrollWheelZoom: false, // avoids hijacking page scroll zoomControl: true }).setView([48.8584, 2.2945], 13); L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }).addTo(map); // let users zoom with the wheel only after they click the map map.on('click', () => map.scrollWheelZoom.enable()); map.on('mouseout', () => map.scrollWheelZoom.disable()); setView takes [latitude, longitude] and a zoom level: 3 for a continent, 12 to 14 for a city, 16 and above for a street. Keeping the attribution line is a licence requirement, not a suggestion. Step 4: Add Markers and Popups L.marker([48.8584, 2.2945]) .addTo(map) .bindPopup( '<strong>Paris office</strong><br>5 Avenue Anatole France<br>' + '<a href="/contact/paris">Opening hours</a>' ); Popups accept raw HTML, so you can drop in an image, a phone link or a button. Add .openPopup() if you want one marker open on load. For permanent labels, use bindTooltip('Paris', { permanent: true }) instead. Step 5: Use Your Own Branded Pin const brandPin = L.icon({ iconUrl: '/img/pin.svg', iconSize: [32, 42], // width, height iconAnchor: [16, 42], // the tip of the pin popupAnchor: [0, -38], // where the popup opens shadowUrl: null }); L.marker([48.8584, 2.2945], { icon: brandPin, title: 'Paris office' }) .addTo(map); iconAnchor is the detail everyone gets wrong. It defines which pixel of your image sits on the coordinate. For a classic teardrop pin, that is the horizontal centre and the bottom edge. Step 6: Loop Through Multiple Locations and Auto Fit the View Hard coding one marker is fine for a contact page. For a store locator, drive the map from data: const locations = [ { name: 'Paris office', lat: 48.8584, lng: 2.2945, url: '/contact/paris' }, { name: 'Lisbon

How to Create an Interactive Map for a Website With Leaflet (No Google Maps API Key) Read More »

About Us

Express Jam Studio was founded in 2004 by John Smith. John had previously worked for a courier company, but he saw an opportunity to start his own business in the web design and development industry.

Contact Info

Copyright © 2022 Express Jam Studio. All Rights Reserved.