/** * jQuery fontIconPicker - v2.3 * * An icon picker built on top of font icons and jQuery * * http://codeb.it/fontIconPicker * * Made by Alessandro Benoit & Swashata * Under MIT License * * {@link https://github.com/micc83/fontIconPicker} * * Modified by Visual Composer Dev Team */ (function ( $ ) { 'use strict'; // Create the defaults once var defaults = { theme: 'fip-vc-theme-grey', // The CSS theme to use with this fontIconPicker. You can set different themes on multiple elements on the same page source: false, // Icons source (array|false|object) emptyIcon: true, // Empty icon should be shown? emptyIconValue: '', // The value of the empty icon, change if you select has something else, say "none" iconsPerPage: 20, // Number of icons per page hasSearch: true, // Is search enabled? searchSource: false, // Give a manual search values. If using attributes then for proper search feature we also need to pass icon names under the same order of source useAttribute: false, // Whether to use attribute selector for printing icons attributeName: 'data-icon', // HTML Attribute name convertToHex: true, // Whether or not to convert to hexadecimal for attribute value. If true then please pass decimal integer value to the source (or as value="" attribute of the select field) allCategoryText: 'From all categories', // The text for the select all category option unCategorizedText: 'Uncategorized', // The text for the select uncategorized option iconDownClass: 'fip-icon-down-dir', // Class for icon down iconUpClass: 'fip-icon-up-dir', // Class for icon up iconLeftClass: 'fip-icon-left-dir', // Class for icon left iconRightClass: 'fip-icon-right-dir', // Class for icon right iconSearchClass: 'fip-icon-search', // Class for search iconCancelClass: 'fip-icon-cancel', // Class for search canceling iconSpinClass: 'fip-icon-spin3', // Class for fip-icon-spin3 iconBlockClass: 'fip-icon-block', // Class for block(none icon) searchPlaceholder: 'Search Icon', // Search icon text placeholder mainClass: 'vc-icons-selector' }; // The actual plugin constructor function Plugin( element, options ) { this.element = $( element ); this.settings = $.extend( {}, defaults, options ); if ( this.settings.emptyIcon ) { this.settings.iconsPerPage --; } this.iconPicker = $( '
', { 'class': this.settings.mainClass, style: 'position: relative', html: '
' + '' + '' + '' + '' + '' + '' + '
' + '' } ); this.iconContainer = this.iconPicker.find( '.fip-icons-container' ); this.searchIcon = this.iconPicker.find( '.selector-search i' ); this.iconsSearched = []; this.isSearch = false; this.totalPage = 1; this.currentPage = 1; this.currentIcon = false; this.initialized = false; this.iconsPaged = false; this.iconsCount = 0; this.open = false; // Set the default values for the search related variables this.searchValues = []; this.availableCategoriesSearch = []; // The trigger event for change this.triggerEvent = null; // Backups this.backupSource = []; this.backupSearch = []; // Set the default values of the category related variables this.isCategorized = false; // Automatically detects if the icon listing is categorized this.selectCategory = this.iconPicker.find( '.icon-category-select' ); // The category SELECT input field this.selectedCategory = false; // false means all categories are selected this.availableCategories = []; // Available categories, it is a two dimensional array which holds categorized icons this.unCategorizedKey = null; // Key of the uncategorized category // Initialize plugin this.quickInit(); } Plugin.prototype = { /** * Quick init */ quickInit: function () { var first = true; // Add the theme CSS to the iconPicker this.iconPicker.addClass( this.settings.theme ); // To properly calculate iconPicker height and width // We will first append it to body (with left: -9999px so that it is not visible) this.iconPicker.css( { left: - 9999 } ).appendTo( 'body' ); var iconPickerHeight = this.iconPicker.outerHeight(), iconPickerWidth = this.iconPicker.outerWidth(); // Now reset the iconPicker CSS this.iconPicker.css( { left: '' } ); // Add the icon picker after the select this.element.before( this.iconPicker ); // Hide source element // Instead of doing a display:none, we would rather // make the element invisible // and adjust the margin this.element.css( { visibility: 'hidden', top: 0, position: 'relative', zIndex: '-1', left: '-' + iconPickerWidth + 'px', display: 'none', height: iconPickerHeight + 'px', width: iconPickerWidth + 'px', // Reset all margin, border and padding padding: '0', margin: '0 -' + iconPickerWidth + 'px 0 0', // Left margin adjustment to account for dangling space border: '0 none', verticalAlign: 'top' } ).hide(); // Set the trigger event if ( ! this.element.is( 'select' ) ) { // Determine the event that is fired when user change the field value // Most modern browsers supports input event except IE 7, 8. // IE 9 supports input event but the event is still not fired if I press the backspace key. // Get IE version // https://gist.github.com/padolsey/527683/#comment-7595 var ieVersion = (function () { var v = 3, div = document.createElement( 'div' ), a = div.all || []; while ( div.innerHTML = '', a[ 0 ] ) { ; } return v > 4 ? v : ! v; }()); var el = document.createElement( 'div' ); this.triggerEvent = (ieVersion === 9 || ! ('oninput' in el)) ? [ 'keyup' ] : [ 'input', 'keyup' ]; // Let's keep the keyup event for scripts that listens to it } this.setSelectedIcon( this.element.val() ); /** * Category changer */ this.selectCategory.on( 'change keyup', $.proxy( function ( e ) { // Don't do anything if not categorized if ( this.isCategorized === false ) { return false; } var targetSelect = $( e.currentTarget ), currentCategory = targetSelect.val(); // Check if all categories are selected if ( targetSelect.val() === 'all' ) { // Restore from the backups // @note These backups must be rebuild on source change, otherwise it will lead to error this.settings.source = this.backupSource; this.searchValues = this.backupSearch; // No? So there is a specified category } else { var key = parseInt( currentCategory, 10 ); if ( this.availableCategories[ key ] ) { this.settings.source = this.availableCategories[ key ]; this.searchValues = this.availableCategoriesSearch[ key ]; } } this.resetSearch(); this.loadIcons(); }, this ) ); /** * On down arrow click */ this.iconPicker.find( '.selector-button' ).on( 'click', $.proxy( function () { if ( ! this.open && first ) { first = false; this.initCategories(); } // Open/Close the icon picker this.toggleIconSelector(); }, this ) ); /** * Next page */ this.iconPicker.find( '.selector-arrow-right' ).on( 'click', $.proxy( function ( e ) { if ( this.currentPage < this.totalPage ) { this.iconPicker.find( '.selector-arrow-left' ).show(); this.currentPage = this.currentPage + 1; this.renderIconContainer(); this.renderIcons(); } if ( this.currentPage === this.totalPage ) { $( e.currentTarget ).hide(); } }, this ) ); /** * Prev page */ this.iconPicker.find( '.selector-arrow-left' ).on( 'click', $.proxy( function ( e ) { if ( this.currentPage > 1 ) { this.iconPicker.find( '.selector-arrow-right' ).show(); this.currentPage = this.currentPage - 1; this.renderIconContainer(); this.renderIcons(); } if ( this.currentPage === 1 ) { $( e.currentTarget ).hide(); } }, this ) ); /** * Realtime Icon Search */ this.iconPicker.find( '.icons-search-input' ).on( 'keyup', $.proxy( function ( e ) { // Get the search string var searchString = $( e.currentTarget ).val(); // If the string is not empty if ( searchString === '' ) { this.resetSearch(); return; } // Set icon search to X to reset search this.searchIcon.removeClass( this.settings.iconSearchClass ); this.searchIcon.addClass( this.settings.iconCancelClass ); // Set this as a search this.isSearch = true; // Reset current page this.currentPage = 1; // Actual search // This has been modified to search the searchValues instead // Then return the value from the source if match is found this.iconsSearched = []; $.grep( this.searchValues, $.proxy( function ( n, i ) { if ( n.toLowerCase().search( searchString.toLowerCase() ) >= 0 ) { this.iconsSearched[ this.iconsSearched.length ] = this.settings.source[ i ]; return true; } }, this ) ); // Filter duplicates this.iconsSearched = this.iconsSearched.filter( this.getOnlyUnique ); // Render icon list this.renderIconContainer(); this.renderIcons(); }, this ) ); /** * Quit search */ this.iconPicker.find( '.selector-search i' ).on( 'click', $.proxy( function () { this.iconPicker.find( '.icons-search-input' ).focus(); this.resetSearch(); }, this ) ); /** * On icon selected */ this.iconContainer.on( 'click', '.fip-box', $.proxy( function ( e ) { this.setSelectedIcon( $( e.currentTarget ).find( 'i' ).attr( 'data-fip-value' ) ); this.toggleIconSelector(); }, this ) ); /** * Stop click propagation on iconpicker */ this.iconPicker.on( 'click', function ( event ) { event.stopPropagation(); return false; } ); /** * On click out */ $( 'html' ).on( 'click', $.proxy( function () { if ( this.open ) { this.toggleIconSelector(); } }, this ) ); }, /** * Init */ init: function () { // Add the theme CSS to the iconPicker this.iconPicker.addClass( this.settings.theme ); // To properly calculate iconPicker height and width // We will first append it to body (with left: -9999px so that it is not visible) this.iconPicker.css( { left: - 9999 } ).appendTo( 'body' ); var iconPickerHeight = this.iconPicker.outerHeight(), iconPickerWidth = this.iconPicker.outerWidth(); // Now reset the iconPicker CSS this.iconPicker.css( { left: '' } ); // Add the icon picker after the select this.element.before( this.iconPicker ); // Hide source element // Instead of doing a display:none, we would rather // make the element invisible // and adjust the margin this.element.css( { visibility: 'hidden', top: 0, position: 'relative', zIndex: '-1', left: '-' + iconPickerWidth + 'px', display: 'none', height: iconPickerHeight + 'px', width: iconPickerWidth + 'px', // Reset all margin, border and padding padding: '0', margin: '0 -' + iconPickerWidth + 'px 0 0', // Left margin adjustment to account for dangling space border: '0 none', verticalAlign: 'top' } ).hide(); // Set the trigger event if ( ! this.element.is( 'select' ) ) { // Determine the event that is fired when user change the field value // Most modern browsers supports input event except IE 7, 8. // IE 9 supports input event but the event is still not fired if I press the backspace key. // Get IE version // https://gist.github.com/padolsey/527683/#comment-7595 var ieVersion = (function () { var v = 3, div = document.createElement( 'div' ), a = div.all || []; while ( div.innerHTML = '', a[ 0 ] ) { ; } return v > 4 ? v : ! v; }()); var el = document.createElement( 'div' ); this.triggerEvent = (ieVersion === 9 || ! ('oninput' in el)) ? [ 'keyup' ] : [ 'input', 'keyup' ]; // Let's keep the keyup event for scripts that listens to it } this.initCategories(); /** * Category changer */ this.selectCategory.on( 'change keyup', $.proxy( function ( e ) { // Don't do anything if not categorized if ( this.isCategorized === false ) { return false; } var targetSelect = $( e.currentTarget ), currentCategory = targetSelect.val(); // Check if all categories are selected if ( targetSelect.val() === 'all' ) { // Restore from the backups // @note These backups must be rebuild on source change, otherwise it will lead to error this.settings.source = this.backupSource; this.searchValues = this.backupSearch; // No? So there is a specified category } else { var key = parseInt( currentCategory, 10 ); if ( this.availableCategories[ key ] ) { this.settings.source = this.availableCategories[ key ]; this.searchValues = this.availableCategoriesSearch[ key ]; } } this.resetSearch(); this.loadIcons(); }, this ) ); /** * On down arrow click */ this.iconPicker.find( '.selector-button' ).on( 'click', $.proxy( function () { // Open/Close the icon picker this.toggleIconSelector(); }, this ) ); /** * Next page */ this.iconPicker.find( '.selector-arrow-right' ).on( 'click', $.proxy( function ( e ) { if ( this.currentPage < this.totalPage ) { this.iconPicker.find( '.selector-arrow-left' ).show(); this.currentPage = this.currentPage + 1; this.renderIconContainer(); this.renderIcons(); } if ( this.currentPage === this.totalPage ) { $( e.currentTarget ).hide(); } }, this ) ); /** * Prev page */ this.iconPicker.find( '.selector-arrow-left' ).on( 'click', $.proxy( function ( e ) { if ( this.currentPage > 1 ) { this.iconPicker.find( '.selector-arrow-right' ).show(); this.currentPage = this.currentPage - 1; this.renderIconContainer(); this.renderIcons(); } if ( this.currentPage === 1 ) { $( e.currentTarget ).hide(); } }, this ) ); /** * Realtime Icon Search */ this.iconPicker.find( '.icons-search-input' ).on( 'keyup', $.proxy( function ( e ) { // Get the search string var searchString = $( e.currentTarget ).val(); // If the string is not empty if ( searchString === '' ) { this.resetSearch(); return; } // Set icon search to X to reset search this.searchIcon.removeClass( this.settings.iconSearchClass ); this.searchIcon.addClass( this.settings.iconCancelClass ); // Set this as a search this.isSearch = true; // Reset current page this.currentPage = 1; // Actual search // This has been modified to search the searchValues instead // Then return the value from the source if match is found this.iconsSearched = []; $.grep( this.searchValues, $.proxy( function ( n, i ) { if ( n.toLowerCase().search( searchString.toLowerCase() ) >= 0 ) { this.iconsSearched[ this.iconsSearched.length ] = this.settings.source[ i ]; return true; } }, this ) ); // Filter duplicates this.iconsSearched = this.iconsSearched.filter( this.getOnlyUnique ); // Render icon list this.renderIconContainer(); this.renderIcons(); }, this ) ); /** * Quit search */ this.iconPicker.find( '.selector-search i' ).on( 'click', $.proxy( function () { this.iconPicker.find( '.icons-search-input' ).focus(); this.resetSearch(); }, this ) ); /** * On icon selected */ this.iconContainer.on( 'click', '.fip-box', $.proxy( function ( e ) { this.setSelectedIcon( $( e.currentTarget ).find( 'i' ).attr( 'data-fip-value' ) ); this.toggleIconSelector(); }, this ) ); /** * Stop click propagation on iconpicker */ this.iconPicker.on( 'click', function ( event ) { event.stopPropagation(); return false; } ); /** * On click out */ $( 'html' ).on( 'click', $.proxy( function () { if ( this.open ) { this.toggleIconSelector(); } }, this ) ); }, initCategories: function () { // If current element is SELECT populate settings.source if ( ! this.settings.source && this.element.is( 'select' ) ) { // Reset the source and searchSource // These will be populated according to the available options this.settings.source = []; this.settings.searchSource = []; // Check if optgroup is present within the select // If it is present then the source has to be grouped if ( this.element.find( 'optgroup' ).length ) { // Set the categorized to true this.isCategorized = true; this.element.find( 'optgroup' ).each( $.proxy( function ( i, el ) { // Get the key of the new category array var thisCategoryKey = this.availableCategories.length, // Create the new option for the selectCategory SELECT field categoryOption = $( '' ).prependTo( this.selectCategory ); // Show it and set default value to all categories this.selectCategory.show().val( 'all' ).trigger( 'change' ); }, /** * Load icons */ loadIcons: function () { // Set the content of the popup as loading this.iconContainer.html( '' ); // If source is set if ( this.settings.source instanceof Array ) { // Render icons this.renderIconContainer(); this.renderIcons(); this.setContainerSelectedItems(); } }, /** * Render icons inside the popup */ renderIconContainer: function () { var offset, iconsPaged = []; // Set a temporary array for icons if ( this.isSearch ) { iconsPaged = this.iconsSearched; } else { iconsPaged = this.settings.source; } // Remove duplicates iconsPaged = [ ...new Set( iconsPaged ) ]; // Count elements this.iconsCount = iconsPaged.length; // Calculate total page number this.totalPage = Math.ceil( this.iconsCount / this.settings.iconsPerPage ); // Hide footer if no pagination is needed if ( this.totalPage > 1 ) { this.iconPicker.find( '.selector-footer' ).show(); } else { this.iconPicker.find( '.selector-footer' ).hide(); } // Set the text for page number index and total icons this.iconPicker.find( '.selector-pages' ).html( this.currentPage + '/' + this.totalPage + ' (' + this.iconsCount + ')' ); // Set the offset for slice offset = (this.currentPage - 1) * this.settings.iconsPerPage; // Should empty icon be shown? if ( this.settings.emptyIcon ) { // Reset icon container HTML and prepend empty icon this.iconContainer.html( '' ); // If not show an error when no icons are found } else if ( iconsPaged.length < 1 ) { this.iconContainer.html( '' ); return; // else empty the container } else { this.iconContainer.html( '' ); } // Set an array of current page icons iconsPaged = iconsPaged.slice( offset, offset + this.settings.iconsPerPage ); this.iconsPaged = iconsPaged; // List icons /*for (var i = 0, item; item = iconsPaged[i++];) { // Set the icon title var flipBoxTitle = item; $.grep(this.settings.source, $.proxy(function (e, i) { if (e === item) { flipBoxTitle = this.searchValues[i]; return true; } return false; }, this)); // Set the icon box $('', { html: '', 'class': 'fip-box', title: flipBoxTitle }).appendTo(this.iconContainer); }*/ }, setContainerSelectedItems: function () { // If no empty icon is allowed and no current value is set or current value is not inside the icon set if ( ! this.settings.emptyIcon && (! this.element.val() || $.inArray( this.element.val(), this.settings.source ) === - 1) ) { // Get the first icon this.setSelectedIcon( this.iconsPaged[ 0 ] ); } else if ( $.inArray( this.element.val(), this.settings.source ) === - 1 ) { // Set empty this.setSelectedIcon(); } else { // Set the default selected icon even if not set this.setSelectedIcon( this.element.val() ); } }, /** * Set Highlighted icon */ setHighlightedIcon: function () { this.iconContainer.find( '.current-icon' ).removeClass( 'current-icon' ); if ( this.currentIcon ) { this.iconContainer.find( '[data-fip-value="' + this.currentIcon + '"]' ).parent( 'span' ).addClass( 'current-icon' ); } }, /** * Set selected icon * * @param {string} theIcon */ setSelectedIcon: function ( theIcon ) { if ( theIcon === this.settings.iconBlockClass ) { theIcon = ''; } // Check if attribute is to be used if ( this.settings.useAttribute ) { if ( theIcon ) { this.iconPicker.find( '.selected-icon' ).html( '' ); } else { this.iconPicker.find( '.selected-icon' ).html( '' ); } // Use class } else { this.iconPicker.find( '.selected-icon' ).html( '' ); } // Set the value of the element and trigger change event this.element.val( (theIcon === '' ? this.settings.emptyIconValue : theIcon ) ).trigger( 'change' ); if ( this.triggerEvent !== null ) { // Trigger other events for ( var eventKey in this.triggerEvent ) { this.element.trigger( this.triggerEvent[ eventKey ] ); } } this.currentIcon = theIcon; this.setHighlightedIcon(); }, /** * Open/close popup (toggle) */ toggleIconSelector: function () { this.open = (! this.open) ? 1 : 0; this.iconPicker.find( '.selector-popup' ).slideToggle( 300 ); this.iconPicker.find( '.selector-button i' ).toggleClass( this.settings.iconDownClass ); this.iconPicker.find( '.selector-button i' ).toggleClass( this.settings.iconUpClass ); if ( this.open ) { this.iconPicker.find( '.icons-search-input' ).focus().select(); if ( ! this.initialized ) { this.renderIconContainer(); this.renderIcons(); this.initialized = true; } } }, renderIcons: function () { for ( var i = 0; i < this.iconsPaged.length; i ++ ) { var item = this.iconsPaged[ i ]; // Set the icon title var flipBoxTitle = item; $.grep( this.settings.source, $.proxy( function ( e, i ) { if ( e === item ) { flipBoxTitle = this.searchValues[ i ]; return true; } return false; }, this ) ); // Set the icon box $( '', { html: '', 'class': 'fip-box', title: flipBoxTitle } ).appendTo( this.iconContainer ); } this.setContainerSelectedItems(); }, /** * Reset search */ resetSearch: function () { // Empty input this.iconPicker.find( '.icons-search-input' ).val( '' ); // Reset search icon class this.searchIcon.removeClass( this.settings.iconCancelClass ); this.searchIcon.addClass( this.settings.iconSearchClass ); // Go back to page 1 and remove back arrow this.iconPicker.find( '.selector-arrow-left' ).hide(); this.currentPage = 1; this.isSearch = false; // Rerender icons this.renderIconContainer(); this.renderIcons(); // Restore pagination if needed if ( this.totalPage > 1 ) { this.iconPicker.find( '.selector-arrow-right' ).show(); } } }; // Lightweight plugin wrapper $.fn.vcFontIconPicker = function ( options ) { // Instantiate the plugin this.each( function () { if ( ! $.data( this, "vcFontIconPicker" ) ) { $.data( this, "vcFontIconPicker", new Plugin( this, options ) ); } } ); // setIcons method this.setIcons = $.proxy( function ( newIcons, iconSearch ) { if ( undefined === newIcons ) { newIcons = false; } if ( undefined === iconSearch ) { iconSearch = false; } this.each( function () { $.data( this, "vcFontIconPicker" ).settings.source = newIcons; $.data( this, "vcFontIconPicker" ).settings.searchSource = iconSearch; $.data( this, "vcFontIconPicker" ).initSourceIndex(); $.data( this, "vcFontIconPicker" ).resetSearch(); $.data( this, "vcFontIconPicker" ).loadIcons(); } ); }, this ); // destroy method this.destroyPicker = $.proxy( function () { this.each( function () { if ( ! $.data( this, "vcFontIconPicker" ) ) { return; } // Remove the iconPicker $.data( this, "vcFontIconPicker" ).iconPicker.remove(); // Reset the CSS $.data( this, "vcFontIconPicker" ).element.css( { visibility: '', top: '', position: '', zIndex: '', left: '', display: 'block', height: '', width: '', padding: '', margin: '', border: '', verticalAlign: '' } ).show(); // destroy data $.removeData( this, "vcFontIconPicker" ); } ); }, this ); // reInit method this.refreshPicker = $.proxy( function ( newOptions ) { if ( ! newOptions ) { newOptions = options; } // First destroy this.destroyPicker(); // Now reset this.each( function () { if ( ! $.data( this, "vcFontIconPicker" ) ) { $.data( this, "vcFontIconPicker", new Plugin( this, newOptions ) ); } } ); }, this ); return this; }; })( jQuery ); # Changelog ## [Unreleased] ## [1.0.4] - 2024-05-02 ### Fixed - Fix implicit nullable deprecation warning for PHP 8.4 ## [1.0.3] - 2022-01-10 ### Changed - Update PHPstan to 1.0 - Switched to GitHub Actions ## [1.0.2] - 2020-07-15 ### Added - Minor performance improvements ## [1.0.1] - 2020-06-16 ### Fixed - When calling `Punycode::decode()`, the case flags array would fail to populate when given an empty array. ## [1.0.0] - 2020-06-09 - Initial release

What Actually Makes a Travel eSIM Worth Your Money?

The Best eSIM for Travel in 2025, Based on Real Trips

The best eSIM for travel can be activated in under two minutes, often before you even board your flight, which saves you from the dreaded airport SIM-card scramble. It works by embedding a digital profile into your phone, so you simply scan a QR code and instantly connect to local networks in over 190 countries without swapping physical cards. This means you keep your home number active for calls and texts while enjoying affordable data abroad, and you can top up or switch plans mid-trip right from an app when you run low.

What Actually Makes a Travel eSIM Worth Your Money?

A travel eSIM is only worth your money if it delivers **reliable coverage** exactly where you’re headed, not just cheap gigs on paper. The best eSIM for travel balances transparent pricing with flexible data top-ups, so you’re not stuck re-buying a plan when you run out mid-trip. Look for instant activation via QR code and the ability to switch networks manually if one operator drops—that alone saves you from roaming black holes. Always check if the plan supports hotspot tethering, because many budget options quietly block it, making your laptop useless. Finally, a worthwhile eSIM offers a clear refund or replacement policy for faulty codes. If you pay for 10 days and lose reception on day two, that’s wasted money. The actual value lies in pay-as-you-go flexibility and multi-country compatibility—not flashy app dashboards. Skip anything that forces you to estimate data upfront without a usage tracker.

Coverage reach: why network switching matters more than total country count

A high country count on a travel eSIM is meaningless if those countries lack consistent signal depth. Network switching matters more than total country count because it determines whether you connect to the strongest local carrier in a given region, not just any available tower. A single-country eSIM with access to three partner networks often outperforms a 100-country plan that relies on one weak roamer per territory. When you cross a border or move from a city to a rural valley, the eSIM must automatically re-evaluate available bands and hop to a denser network. Without that arbitration, you face dead zones, throttled speeds, or a persistent “no service” indicator despite being inside a covered nation. Prioritize plans that explicitly advertise multi-network fallback per country, as this directly governs your real-world connectivity.

Data-only vs. calls-and-text plans: what you lose when you save a buck

Opting for a data-only travel eSIM trims your bill, but it quietly strips away your safety net. That “calls-and-texts” layer isn’t just legacy tech—it’s your lifeline for two-factor authentication, booking confirmations, and urgent chats with your hotel. Data-only plans leave you stranded when a banking app demands an SMS code or a local driver calls to find your gate. You’ll burn data on VoIP workarounds, only to hit dead ends with automated systems. The real kicker? Roaming calls back home often cost less than the data you’ll waste troubleshooting. A few dollars saved upfront converts directly into hours of connectivity juggling acts.

Hotspot tethering limits that secretly control your laptop workflow

Many travel eSIMs cap tethering speeds after a modest data threshold, even when your phone’s direct connection remains full-speed. This hidden limit silently throttles your laptop’s video calls and large uploads, turning a “unlimited” plan into a slide-show workflow. Before buying, check the fair-use policy for **tethering-specific speed reductions**, not just total data. A plan that allows 10 GB at 5G for your phone may drop your laptop to 512 kbps after 3 GB of hotspot use. Read the fine print or test with a speed test immediately after connecting.

Q: How do I spot hidden hotspot tethering limits before they stall my laptop?
A: Look for the phrase “mobile hotspot data” in the plan details—many providers list a separate, smaller allowance. Also, search the carrier’s support page for “tethering throttle” to see if the speed cap applies only after a certain GB, and compare that number against your typical daily laptop usage.

How to Compare Travel eSIM Prices Without Getting Burned by “Unlimited” Plans

When hunting for the best eSIM for travel, never treat “unlimited” as a flat price point—scrutinize the fair-use cap, which often throttles speeds to a glacial 256 kbps after a few GB, making maps and messaging useless. Compare the *actual cost per usable gigabyte* at full speed, not the headline figure, because a 10 GB plan at $20 can beat a “truly unlimited” $50 option for most trips. Check whether the allowance is daily, weekly, or total, as some plans reset at midnight, draining your data overnight on background updates. Prioritize providers that publish their deprioritization policy and offer a top-up option at the same rate; otherwise, you’ll pay premium per-GB prices mid-journey. Finally, calculate your real usage from past trips—streaming and video calls burn through caps fast—so you buy just enough, not the plan that looks cheapest per day, but the one that matches your actual behavior.

best eSIM for travel

Fair-use thresholds and throttling speeds nobody mentions on the landing page

Dig past the “unlimited” badge, and you’ll find fair-use thresholds that quietly cap your high-speed data—often at 20–50 GB. Hit that invisible line, and the throttle isn’t a gentle dip; it’s a cliff back to 128 kbps, which renders maps and messaging painfully slow. Most landing pages hide the full policy in a PDF, so search the FAQ for “fair use” or “speed limit.” Also check whether the throttle applies per-day or for the entire trip—some plans reset daily, others lock you in slow mode for a week. A 30 GB “unlimited” plan from one provider might beat a 100 GB plan from another if the latter throttles harder after 5 GB.

The real cost of top-up bundles versus buying a fresh plan mid-trip

When your data runs low mid-trip, the math can surprise you. Top-up bundles often look cheaper upfront, but they usually carry a higher per-GB price than the original plan you bought. Before tapping “renew,” check the price of a fresh plan from the same provider—or even a different eSIM—because topping up versus buying fresh can double your cost. A fresh travel eSIM often includes a nice bonus data chunk for the same price as a tiny top-up, making it the smarter value. Always compare the per-GB rate of both options in the app first, then choose what stretches your budget further.

Why regional plans beat single-country options for multi-stop itineraries

For multi-stop itineraries, regional plans frequently outperform single-country options because they eliminate the friction of re-purchasing connectivity at every border crossing. A single-country eSIM forces you to monitor data exhaustion and swap profiles mid-journey, while a regional plan provides one continuous, predictable data pool across your entire route. This consolidated approach also mitigates the risk of coverage gaps, as the network handoff between neighboring countries is typically smoother within a single regional provider’s footprint. Crucially, the cost-per-gigabyte on a regional package often undercuts the sum of separate single-country plans, especially when you factor in the occasional unused data from each nation-specific purchase. For seamless transitions between destinations, multi-country eSIM coverage is simply more robust for the traveler who values time over micromanagement.

Regional plans win because they offer one integrated data allowance, reduce setup overhead, and are more economical than stacking multiple single-country packages on a multi-stop route.

Which Devices Can Handle a Travel eSIM and How to Install One in 60 Seconds

best eSIM for travel

For the best eSIM for travel, check device compatibility first: most unlocked iPhone models from XS onward, Google Pixel 3a and newer, plus recent Samsung Galaxy S and Note flagships support eSIM. To install one in 60 seconds, scan the QR code your provider emails you—or manually enter the SM-DP+ address—then confirm the activation label. Do this before you leave home so you can switch to the travel profile immediately upon landing. In settings, set the eSIM as your primary data line, keeping your physical SIM active for calls if desired. If your phone lacks eSIM, choose a provider offering a physical SIM fallback, as that remains the workaround for tablets or older models. Always verify IMEI support on the carrier’s site before purchasing.

Smartphone compatibility pitfalls: carrier locks and older Android models

Carrier locks remain the first hard stop for travel eSIMs: a phone sold locked to a domestic operator often rejects foreign profiles even if the hardware is capable. Unlock it permanently before departure, or the installation will fail at the provisioning stage. Older Android models introduce a second pitfall—many lack the required eSIM hardware entirely, and even those with it (like the Pixel 3 or Galaxy S10) may run outdated firmware that breaks profile downloads. Android’s fragmented support means checking the exact model number, not just the brand, against the eSIM provider’s compatibility list is non-negotiable. Carrier locks and legacy Android firmware cause most mid-trip activation failures.

Q: Can a carrier-locked older Android still use a travel eSIM? Only if the lock is removed first and the OS version still supports eSIM management—otherwise, both issues must be resolved before travel.

Scanning the QR code vs. manual APN setup: what to do when one fails

When the QR code scan fails on your travel eSIM, don’t panic—proceed to manual APN setup to regain control. A QR code is convenient, but it’s just a shortcut; your carrier’s confirmation email always includes the SM-DP+ address and activation code. Punch those into your device’s “Add Cellular Plan” menu instead. If the APN fails, check for a trailing space or a wrong server name—typos are the #1 culprit. Then, toggle airplane mode on and off to force a refresh. One method always works, so you’re never stranded, just a few taps from a working connection.

Q: What if both QR scanning and manual APN setup fail?
A: Restart the phone completely, then re-enter the APN details slowly, matching every character exactly as shown in your eSIM provider’s email—case and slashes included. If it still won’t connect, erase that eSIM profile and re-scan the QR code with your camera tilted 45 degrees for better focus.

best eSIM for travel

Watching your data balance: the best apps for real-time tracking on the go

Keeping an eye on your remaining megabytes is the secret to stress-free travel, and the best eSIM for travel apps make it effortless. Native phone settings offer a basic counter, but dedicated tools like **My Data Manager** or GlassWire provide real-time alerts and per-app breakdowns, so you know exactly which app is guzzling data. For a simpler approach, the eSIM provider’s own app (like Airalo or Holafly) usually has a live dashboard right on the home screen, updating after every sync. Set a low-data warning before you leave Wi-Fi, and check the widget on your lock screen to avoid surprises.

Q: What is the quickest way to check data balance while roaming?
A: Open your eSIM provider’s app—it auto-refreshes your usage within seconds, and many now offer a home screen widget so you never have to dig through menus.

Physical SIM vs. Travel eSIM: Which One Fails Less Often in Remote Areas

When you’re truly off-grid, a physical SIM usually fails less often than a travel eSIM, because it locks onto the strongest local tower without needing a data profile refresh. Remote valleys or mountain passes often have only one carrier’s signal, and a physical SIM’s automatic roaming picks it up faster, while an eSIM may stubbornly stick to a weak partner network. That said, the best eSIM for travel shines in spotty but multi-carrier zones, letting you manually switch profiles in seconds—something a physical SIM can’t do without buying a new card. The real killer for eSIMs is no initial signal download, so if you’re heading truly remote, pre-load a backup physical SIM. For everything else, an eSIM’s flexibility beats the old card’s reliability when you’re not in a dead zone.

Roaming handoff delays and how eSIM profile switching fixes dropped connections

Roaming handoff delays happen when your phone clings to a weak tower before switching networks, causing that endless “SOS” or frozen loading screen. Physical SIMs often force you to manually search for a carrier, which can take minutes in remote zones. A travel eSIM fixes this by letting you pre-load multiple local profiles, so when one network drops, your device instantly jumps to a backup profile without re-authentication. This instant profile switching prevents dropped connections because the eSIM’s digital handshake is faster than a physical card’s manual re-registration. The trick is picking an eSIM provider that supports automatic network failover, not just static profiles.

Q: How does eSIM profile switching reduce roaming handoff delays?
A: It bypasses the slow SIM-card re-registration process—profiles are already saved, so the phone just swaps data paths in under a second, unlike physical SIMs that might take 30+ seconds re-searching towers.

best eSIM for travel

Battery drain comparisons between dual-SIM active mode and single eSIM usage

Running a physical SIM alongside a travel eSIM in dual-SIM active mode means two radios constantly pinging towers, which visibly accelerates battery drain—often 15–20% faster by evening. A single eSIM profile, by contrast, keeps only one modem active, so your phone sips power instead of gulping it. That’s the key battery drain difference between dual-SIM active and single eSIM: fewer signal searches and less network handover overhead. In remote areas where signal is weak, dual-SIM active mode forces both connections to boost power repeatedly, draining you quicker. For travel, sticking to one eSIM alone stretches your charge noticeably, especially during long hiking or transit days.

best eSIM for travel

Emergency backup tricks: keeping a local SIM slot free for worst-case scenarios

Forget dual-SIM convenience—keeping a local SIM slot physically free is your ultimate emergency lifeline. When your travel eSIM’s carrier loses a roaming partner or your profile corrupts mid-hike, a dormant slot does nothing. Instead, reserve it exclusively for a prepaid local micro-SIM purchased at a village kiosk. That tiny plastic card bypasses all remote-area network negotiation failures because it connects directly to the region’s strongest tower. Before departing, test that your eSIM works in the primary slot while the secondary stays empty, then carry a paperclip to eject the tray. If your eSIM fails, you’re not troubleshooting—you’re swapping in a five-dollar card and getting bars in under two minutes.

Quick Answers to the Most Common Travel eSIM Headaches

The biggest travel eSIM headache is usually installation, but the best eSIM for travel providers offer a simple fix: scan a QR code before you leave home, and it activates only when you hit your destination’s network—no hunting for Wi-Fi at arrivals. Another common pain point is data running out mid-trip; top-tier plans let you top up instantly through an app, not a storefront. Poor connectivity? Choose a provider that auto-switches to the strongest local network. Finally, if you panic about billing, remember that most reliable eSIMs are prepaid with zero roaming surprises, and support is often one tap away in-app. These quick answers to the most common travel eSIM headaches boil down to buying from a reputable brand that prioritizes seamless activation, flexible data, and live chat help.

Can you keep your WhatsApp number active while using a travel data plan?

Yes, you can keep your WhatsApp number active with a travel eSIM because WhatsApp ties your account to your SIM’s identity, not an active cellular signal. As long as you installed the eSIM that provides your travel data plan, your phone will connect to the internet, and WhatsApp will verify your existing number automatically without needing your original physical SIM inserted. The key requirement is that your travel data plan includes a phone number (most eSIM data-only plans do not assign one, which can block verification if you reinstall the app). To avoid issues, keep your original SIM installed for a minute at your destination to let WhatsApp confirm the number, then switch to the eSIM’s data-only profile—your chats and calls will remain active over Wi-Fi or the travel data plan.

best eSIM for travel

What happens if you run out of data at 2 AM in a foreign city?

Running out of data at 2 AM usually means your map freezes mid-navigation and your ride-hailing app goes blank—not ideal. But with a travel eSIM, you’re not stuck. Most providers let you buy a small top-up pack in under two minutes, right from their app, using the city’s Wi-Fi at a 24-hour café or hotel lobby. Alternatively, your eSIM’s management dashboard might offer a “low data” mode or instant emergency bundle. Quick eSIM data top-ups save you from wandering dark streets or overpaying for a roaming pass from a local kiosk.

  • Check if your eSIM app supports one-tap “emergency data” for immediate reconnection.
  • Save offline maps before you travel—they work even with zero signal.
  • Locate a 24-hour McDonald’s or train station for free Wi-Fi to process a purchase.
  • Use your eSIM’s SMS feature (if included) to text for help without data.

Do refund policies actually work when your route changes mid-vacation?

When your travel route shifts unexpectedly, refund policies for eSIMs are hit-or-miss, so check the fine print before you buy. Most providers only credit unused data if you cancel within 24 hours of purchase; mid-trip changes rarely qualify because your eSIM is already activated. A few leading baztel.co brands, however, offer a prorated credit for leftover gigabytes if you cross into a new country and your regional plan no longer matches. The key is whether the policy covers “route changes” explicitly—“no-refund after activation” clauses dominate the market, but flexible operators like Airalo and Holafly do grant partial refunds if you contact support with proof of your altered itinerary. Always read the terms under “Refunds” before committing, and keep your confirmation email handy to dispute a denied claim.

Scroll to Top