Auto resizing the SELECT element according to selected OPTION's width
Asked Answered
S

15

41

I've got this select element with different option in it. Normally the select element would get its width from the biggest option element, but I want the select element to have the default option value's width which is shorter. When the user selects another option, the select element should resize itself so the whole text is always visible in the element.

$(document).ready(function() {
    $('select').change(function(){
        $(this).width($('select option:selected').width());
    });
});

Problem:

  • On Chrome (Canary) it always gets a width returned of 0.
  • On Firefox the width get's added and not resized when selected a shorter option.
  • Safari: Same result as Chrome.

Demo @ JSFiddle

Straley answered 20/11, 2013 at 9:4 Comment(4)
There is no reliable way to get the width of the option element. You could instead get the length of the text property and guesstimate the width required based on that.Pam
@RoryMcCrossan I thought of that too but wouldn't get different results on different browser screen resolution settings? I also found this possible resolution: jsfiddle.net/zERB7/3Straley
simple solution here: https://mcmap.net/q/261121/-how-to-expand-39-select-39-option-width-after-the-user-wants-to-select-an-optionUndermost
Here's a more modern vanilla JS approach to solve this: https://mcmap.net/q/260145/-auto-resizing-the-select-element-according-to-selected-option-39-s-widthNyasaland
B
50

You are right there is no easy or built-in way to get the size of a particular select option. Here is a JsFiddle that does what you want.

It is okay with the latest versions of Chrome, Firefox, IE, Opera and Safari.

I have added a hidden select #width_tmp_select to compute the width of the visible select #resizing_select that we want to be auto resizing. We set the hidden select to have a single option, whose text is that of the currently-selected option of the visible select. Then we use the width of the hidden select as the width of the visible select. Note that using a hidden span instead of a hidden select works pretty well, but the width will be a little off as pointed out by sami-al-subhi in the comment below.

$(document).ready(function() {
  $('#resizing_select').change(function(){
    $("#width_tmp_option").html($('#resizing_select option:selected').text()); 
    $(this).width($("#width_tmp_select").width());  
  });
});
#resizing_select {
  width: 50px;
} 
 
#width_tmp_select{
  display : none;
} 
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>

<select id="resizing_select">
  <option>All</option>
  <option>Longer</option>
  <option>A very very long string...</option>
</select>

<select id="width_tmp_select">
  <option id="width_tmp_option"></option>
</select>
Blanketyblank answered 20/11, 2013 at 9:28 Comment(5)
Your fiddle link is the same as the OPs.Pam
good approach but you have to make sure that <option> text has the same font/size as of the <span>. If you remove #width_tmp{display : none;}, you will find that <span> is larger. This will result in larger <select> than it should be. The longer the text the larger empty space in the <select>.Awning
Here's a pure javascript attempted version of this: jsfiddle.net/FSsG8/635 You you also might get some traction by attempting to match the font between the two selects, somewhat similar: #6386033Munificent
check my solution, it's much more simple and neat :) https://mcmap.net/q/261121/-how-to-expand-39-select-39-option-width-after-the-user-wants-to-select-an-optionUndermost
Here's a more modern vanilla JS approach to solve this: https://mcmap.net/q/260145/-auto-resizing-the-select-element-according-to-selected-option-39-s-widthNyasaland
D
21

Here's a plugin I just wrote for this question that dynamically creates and destroys a mock span so it doesn't clutter up your html. This helps separate concerns, lets you delegate that functionality, and allows for easy reuse across multiple elements.

Include this JS anywhere:

(function($, window){
  $(function() {
    let arrowWidth = 30;

    $.fn.resizeselect = function(settings) {  
      return this.each(function() { 

        $(this).change(function(){
          let $this = $(this);

          // get font-weight, font-size, and font-family
          let style = window.getComputedStyle(this)
          let { fontWeight, fontSize, fontFamily } = style

          // create test element
          let text = $this.find("option:selected").text();
          let $test = $("<span>").html(text).css({
            "font-size": fontSize, 
            "font-weight": fontWeight, 
            "font-family": fontFamily,
            "visibility": "hidden" // prevents FOUC
          });

          // add to body, get width, and get out
          $test.appendTo($this.parent());
          let width = $test.width();
          $test.remove();

          // set select width
          $this.width(width + arrowWidth);

          // run on start
        }).change();

      });
    };

    // run by default
    $("select.resizeselect").resizeselect();                       
  })
})(jQuery, window);

You can initialize the plugin in one of two ways:

  1. HTML - Add the class .resizeselect to any select element:

    <select class="btn btn-select resizeselect">
       <option>All</option>
       <option>Longer</option>
       <option>A very very long string...</option>
     </select>
     
  2. JavaScript - Call .resizeselect() on any jQuery object:

    $("select").resizeselect()

Demo in jsFiddle and StackSnippets:

(function($, window){
  $(function() {
    let arrowWidth = 30;

    $.fn.resizeselect = function(settings) {  
      return this.each(function() { 

        $(this).change(function(){
          let $this = $(this);

          // get font-weight, font-size, and font-family
          let style = window.getComputedStyle(this)
          let { fontWeight, fontSize, fontFamily } = style

          // create test element
          let text = $this.find("option:selected").text();
          let $test = $("<span>").html(text).css({
            "font-size": fontSize, 
            "font-weight": fontWeight, 
            "font-family": fontFamily,
            "visibility": "hidden" // prevents FOUC
          });

          // add to body, get width, and get out
          $test.appendTo($this.parent());
          let width = $test.width();
          $test.remove();

          // set select width
          $this.width(width + arrowWidth);

          // run on start
        }).change();

      });
    };

    // run by default
    $("select.resizeselect").resizeselect();                       
  })
})(jQuery, window);
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>

<select class="resizeselect">
  <option>All</option>
  <option>Longer</option>
  <option>A very very long string...</option>
</select>

Updated to include sizing suggestions from Garywoo & Eric Warnke

Dysphoria answered 3/2, 2015 at 23:26 Comment(6)
I would suggest changing the line var $test = $("<span>").html(text); to var $test = $("<span style=\"font-size:"+$this.css("font-size")+"\">").html(text); to ensure that the font size of the temporary span that is created matches that of the select element. If the font size of the span is smaller or larger, the dynamic resizing will be offset by the difference in size.Palaearctic
Works mostly! My default CSS using Bootstrap would always set the width of a select element to 100%, which returned incorrect widths. I changed the append from body to #myForm and $("<span>") to $('<span style="visibility:hidden">') just for my peace of mind.Conlon
unfortunately it does not work on Ipad/Android tablets, any ideas why? I thought it could be that the font-size of the option is different in those devices but if I set a static font-size it does not solve itMotivity
i fixed the above by giving a static font-size to the form element, what i was suggesting above was to give a static font-size on the script iselfMotivity
check my solution, it's much more simple and neat :) https://mcmap.net/q/261121/-how-to-expand-39-select-39-option-width-after-the-user-wants-to-select-an-optionUndermost
Here's a more modern vanilla JS approach to solve this: https://mcmap.net/q/260145/-auto-resizing-the-select-element-according-to-selected-option-39-s-widthNyasaland
O
13

This working solution makes use here of a temporary auxiliary select into which the selected option from the main select is cloned, such that one can assess the true width which the main select should have.

The nice thing here is that you just add this code and it's applicable to every selects, thus no need to ids and extra naming.

$('select').change(function(){
  var text = $(this).find('option:selected').text()
  var $aux = $('<select/>').append($('<option/>').text(text))
  $(this).after($aux)
  $(this).width($aux.width())
  $aux.remove()
}).change()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select>
  <option>REALLY LONG TEXT, REALLY LONG TEXT, REALLY LONG TEXT</option>
  <option>ABCDEFGHIJKL</option>
  <option>ABC</option>
</select>
Odoriferous answered 25/3, 2019 at 18:0 Comment(0)
K
6

A Pure Javascript 2022 version based on @Kmas's answer.


function resize(event) {
  const fakeEl = document.querySelector('#fakeEl');
  const option = event.target.options[event.target.selectedIndex];
  
  fakeEl[0].innerHTML = event.target.value;
  event.target.style.width = fakeEl.getBoundingClientRect().width + 'px';
}

for (let e of document.querySelectorAll('select.autoresize')) {
  e.onchange = resize;
  e.dispatchEvent(new Event('change'));
}
<select class='autoresize'>
    <option>Foo</option>
    <option>FooBar</option>
    <option>FooBarFooBarFooBar</option>
</select>

<select id='fakeEl' style='visibility: hidden'><option></option></select>

You Might Not Need Jquery
Klagenfurt answered 20/4, 2022 at 9:26 Comment(0)
N
5

Here's a more modern vanilla JavaScript approach to solve this. It's more or less the same principle as in this answer just without jQuery.

  1. Get the select element and listen for changes on it.
  2. Create a new select element and option and pass the text of the current selectedIndex to the option.
  3. Add position: fixed and visibility: hidden styles to the new select element. This ensures, that it is not affecting your layout but its bounding box can still be measured.
  4. Append the option to the select.
  5. Append the select to the original select element.
  6. Get the needed dimensions of that new one using getBoundingClientRect().width
  7. Set the width of the original one based on the dimensions of the new one.
  8. Remove the new one.
  9. Dispatch a change event to trigger this logic initially.

const select = document.querySelector('select')

select.addEventListener('change', (event) => {
  let tempSelect = document.createElement('select'),
      tempOption = document.createElement('option');

  tempOption.textContent = event.target.options[event.target.selectedIndex].text;
  tempSelect.style.cssText += `
      visibility: hidden;
      position: fixed;
      `;
  tempSelect.appendChild(tempOption);
  event.target.after(tempSelect);
  
  const tempSelectWidth = tempSelect.getBoundingClientRect().width;
  event.target.style.width = `${tempSelectWidth}px`;
  tempSelect.remove();
});

select.dispatchEvent(new Event('change'));
<select>
  <option>Short option</option>
  <option>Some longer option</option>
  <option>A very long option with a lot of text</option>
</select>
Nyasaland answered 24/4, 2021 at 6:32 Comment(1)
I needed to add event.target.classList.forEach((className) => tempSelect.classList.add(className)) as well, because the size was a bit offBekha
P
2

Try the following simple JavaScript and convert it in jQuery :)

<html><head><title>Auto size select</title>

<script>
var resized = false;

function resizeSelect(selected) {
  if (resized) return;
  var sel = document.getElementById('select');
  for (var i=0; i<sel.options.length; i++) {
    sel.options[i].title=sel.options[i].innerHTML;
    if (i!=sel.options.selectedIndex) sel.options[i].innerHTML='';
  }
  resized = true;
  sel.blur();
}

function restoreSelect() {
  if (!resized) return;
  var sel = document.getElementById('select');
  for (var i=0; i<sel.options.length; i++) {
    sel.options[i].innerHTML=sel.options[i].title;
  }  
  resized = false;
}
</script>

</head>
<body onload="resizeSelect(this.selectedItem)">
<select id="select" 
  onchange="resizeSelect(this.selectedItem)"
  onblur="resizeSelect(this.selectedItem)"
  onfocus="restoreSelect()">
<option>text text</option>
<option>text text text text text</option>
<option>text text text </option>
<option>text text text text </option>
<option>text</option>
<option>text text text text text text text text text</option>
<option>text text</option>
</select>
</body></html>

Here is a jsfiddle for it: https://jsfiddle.net/sm4dnwuf/1/

Basically what it does is temporarily remove unselected elements when it is not in focus (causing it to size to just the size of the selected).

Prickett answered 20/6, 2014 at 22:22 Comment(0)
M
1

You can use a simple function:

// Function that helps to get the select element width.
$.fn.getSelectWidth = function() {
  var width = Math.round($(this).wrap('<span></span>').width());
  $(this).unwrap();
  return width;
}

Then just use it on your form select element:

$(this).getSelectWidth();
Mixture answered 12/8, 2014 at 12:46 Comment(0)
D
1

Here's yet another plain jQuery solution:

$('#mySelect').change(function(){
    var $selectList = $(this);

    var $selectedOption = $selectList.children('[value="' + this.value + '"]')
        .attr('selected', true);
    var selectedIndex = $selectedOption.index();

    var $nonSelectedOptions = $selectList.children().not($selectedOption)
        .remove()
        .attr('selected', false);

    // Reset and calculate new fixed width having only selected option as a child
    $selectList.width('auto').width($selectList.width());

    // Add options back and put selected option in the right place on the list
    $selectedOption.remove();
    $selectList.append($nonSelectedOptions);
    if (selectedIndex >= $nonSelectedOptions.length) {
         $selectList.append($selectedOption);
    } else {
         $selectList.children().eq(selectedIndex).before($selectedOption);
    }
});
Dessau answered 2/10, 2017 at 8:41 Comment(0)
C
0

Here is how I've achieved the following behavior:

  1. On document load: The select's width is already based on the width of the selected option (repainting does not occur).

  2. On change: The select's width is updated to the width of the newly selected option.

/* WIDTH, ADJUSTMENT, CONTENT BASED */
$( document ).on( 'change', '.width-content-based', function(){
	let text_modified_font;
	let text_modified_string;
	let descendants_emptied_elements;
	
	text_modified_font =
		$( this ).css( 'font-weight' ) + ' ' + $( this ).css( 'font-size' ) + ' ' + $( this ).css( 'font-family' );
	
	if
	(
		$( this ).is( 'select' )
	)
	{
		text_modified_string =
			$( this ).find( ':selected' ).text();
		
		descendants_emptied_elements =
			$( this ).find( '[data-text]' );
	}
	
	else
	{
		text_modified_string =
			$( this ).val();
	}
	
	$( this ).css( { 'width': text_width_estimate( text_modified_string, text_modified_font ) + 'px' } );
	
	if
	(
		descendants_emptied_elements.length
	)
	{
		descendants_emptied_elements.each( function(){
			$( this ).text( $( this ).attr( 'data-text' ) ).removeAttr( 'data-text' );
		});
	}
});

/* DOCUMENT, ON READY */
$( document ).ready( function(){
	
	$( '.width-content-based' ).trigger( 'change' );
	
});

/* TEXT WIDTH ESTIMATE */ function text_width_estimate( text, font ) { let canvas = text_width_estimate.canvas || ( text_width_estimate.canvas = document.createElement( 'canvas' ) ); let context = canvas.getContext( '2d' ); context.font = font; let metrics = context.measureText( text ); return metrics.width + 3; }
select { -webkit-appearance: none; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<select class="width-content-based">
	<option value="AF" data-text="Afghanistan"></option>
	<option value="AX" data-text="Åland Islands"></option>
	<option value="AL" selected>Albania</option>
</select>
Cosentino answered 28/4, 2020 at 9:44 Comment(1)
It's hard to read your code example with its formatting - it might be a good idea to clean it up a bit :)Cleanup
P
0

Here's an example of one way to start with a select box auto sized and then how to auto adjust the width when a new box is selected.

Just copy and paste this into a visual studio code document and call it index.html and double click the file (for super noobs).

 $(document).ready(function() {
    // Auto set the width of the select box for Manufacture on startup
    $("#width_tmp_option").html($('#Manufacture')[0][0].label); 
      $('#Manufacture').width($('#width_tmp_select').width());

    // Adust the width of the select box each time Manufacture is changed
    $('#Manufacture').change(function(){
      $("#width_tmp_option").html($('#Manufacture option:selected').text()); 
      $(this).width($("#width_tmp_select").width());
    });
});
#Manufacture {
  font-size:17px;
} 
#width_tmp_select{
  display : none;
  font-size:17px;
} 
<!-- JS (Jquery) -->
<script src="https://code.jquery.com/jquery-3.6.0.js"
integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk="
crossorigin="anonymous"></script>
<!-- HTML -->
<!-- These are hidden dummy selects that I use to store some html in them and then retrieve the width to resize my select boxes. -->
<select id="width_tmp_select">
  <option id="width_tmp_option"></option>
</select>

<select id="Manufacture">
    <option>Toyota</option>
    <option>Ford</option>
    <option>Lamborghini</option>
</select>
Player answered 19/6, 2021 at 18:50 Comment(0)
L
0

I transformed João Pimentel Ferreira's jquery answer to vanilla js for anyone who needs a plain js solution

function changeWidth() {
  let ghostSelect = document.createElement('select');
  const select = document.getElementById('select');
  var x = select.options[select.selectedIndex].text;
  
  const ghostOption = document.createElement("option");
  ghostOption.setAttribute("value", x);
  var t = document.createTextNode(x);
  ghostOption.appendChild(t);
  ghostSelect.appendChild(ghostOption);
  window.document.body.appendChild(ghostSelect)
  select.style.width = ghostSelect.offsetWidth + 'px';
    window.document.body.removeChild(ghostSelect)
}
    <select id="select" onChange="changeWidth()">
        <option value="all">Choose one</option>
        <option value="1">aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</option>
        <option value="2">bbbbbbbbbbbbbbb</option>
        <option value="3">cccc</option>
        <option value="4">ddd</option>
        <option value="5">ee</option>
        <option value="6">f</option>
    </select>

So what it actually does is that it creates a dummy select with only the selected option, adds it temporarily to the DOM, then calculates the width, sets the original's select width to the new width and then it removes the dummy select from the DOM.

Lalonde answered 20/4, 2022 at 9:19 Comment(0)
C
0

try this:

const select = $('select');

function calcTextWidth(text, font) {
    let canvas = calcTextWidth.canvas || (calcTextWidth.canvas = document.createElement("canvas"));
    let context = canvas.getContext("2d");
    context.font = font;
    let metrics = context.measureText(text);
    return metrics.width;
}

select.change(function () {
    select.width(calcTextWidth(select.find(":selected").text(), 'normal normal 500 15px sans-serif'));
});
Carmeliacarmelina answered 20/4, 2022 at 9:44 Comment(0)
M
0

Using OffscreenCanvas to measure selected option text with, and then updating select element with

      function updateWidth(el) {
        const text = el.selectedOptions[0].text;
        const canvas = new OffscreenCanvas(0, 0);
        const ctx = canvas.getContext('2d')!;
        ctx.font = getComputedStyle(el).font;
        const width = ctx.measureText(text).width;
        const padding = 35;
        el.style.width = width + padding + 'px';
      }

      const select = document.querySelector('select');
      updateSize(select);
      select.addEventListener('change', evt => updateSize(evt.target));
Monger answered 18/10, 2022 at 20:22 Comment(0)
E
0

This is the best answer here. It's pure modern vanilla JavaScript.

There's no need for JQuery or a permanent invisible <select> within your DOM:

None of the other vanilla JavaScript answers worked 100% correctly in all cases. This method takes many things into account such as font, padding, border, etc.

function resize(event) {
  const fakeEl = document.createElement('select');
  const option = event.target.options[event.target.selectedIndex];

  fakeEl.style.visibility = 'hidden';
  fakeEl.style.position = 'absolute';
  fakeEl.style.top = '-9999px';
  fakeEl.style.left = '-9999px';
  fakeEl.style.width = 'auto';
  fakeEl.style.font = window.getComputedStyle(event.target).font;
  fakeEl.style.padding = window.getComputedStyle(event.target).padding;
  fakeEl.style.border = window.getComputedStyle(event.target).border;

  const fakeOption = document.createElement('option');
  fakeOption.innerHTML = option.innerHTML;
  fakeEl.appendChild(fakeOption);
  document.body.appendChild(fakeEl);

  event.target.style.width = fakeEl.getBoundingClientRect().width + 'px';
  fakeEl.remove();
}

for (let e of document.querySelectorAll('select.autoresize')) {
  e.onchange = resize;
  e.dispatchEvent(new Event('change'));
}
<select class='autoresize'>
  <option>Foo</option>
  <option>FooBar</option>
  <option>FooBarFooBarFooBar</option>
</select>
Endoparasite answered 1/7, 2024 at 23:48 Comment(0)
L
-4

You can try this also

select option{width:0; }
select option:hover{width:auto;}
Lactation answered 26/2, 2014 at 9:59 Comment(1)
This has been downvoted but it does seem to work in Firefox (not Chrome).Astounding

© 2022 - 2025 — McMap. All rights reserved.