How to calculate and set pixel width of a TextField, DatePicker etc based on content and label

By default, Vaadin columns have a fixed width. This is pretty wide, and is OK in a Form-style layout.

I want to render fields horizontally in a wrapping div, and if the fields are wider then they have to be I’m wasting space.
I want the field to be just wide enough to fit the (translated) label + a width for the input that we calculate in various ways + vaadin chrome

In our Vaadin 8 app we have a table with the pixel width of all 256 ISO8859-1 characters of our font, and we use that to calculate a pixel width of any string. It works resonably well, but it is janky.
We can do the same in Vaadin20+, but I wondered if there was a better way now.

Grid columns have col.setAutoWidth(true), which calculates width both from data and header. It is a shame there isn’t anything similar for input fields.

Is it possible to use some textField.getElement().executeJs(“…”) to set the pixel width to some expression that uses the actual font info in the browser?
Or, can it be styled with some fit-content?

Attached screendump from our Vaadin8 app shows an example. Note that the date fields are just wide enough to display “”, except when the label is wider

Maybe you can get some ideas from here: textbox - Calculate text width with JavaScript - Stack Overflow

There’s also CSS and the ch unit: What is the CSS ‘ch’ Unit? – Eric’s Archived Thoughts

I found that stackoverflow page too. I had hoped someone else had already used it in a vaadin context :slightly_smiling_face:

Using “ch” unit doesn’t really work. If I do:

textField.setWidth(textField.getLabel().length(), Unit.CH)
this label
This is a really long label
would make the field as wide as this:

Yes, the CH unit works if you have a fixed-width font

ch units should give you a pretty good approximation actually, as it’s based on the width of the 0 glyph, which in most fonts is very close to the average char width.

how well it matches will of course vary from label to label

There is a way, with just CSS, to force the width of a text field to be as wide as the label: --vaadin-field-default-width: min-content;

So that leaves us with how to ALSO take into account some appropriate width for the input.

And I think ch units are the closest you can get to that with CSS, because the html element doesn’t let you do that min-content trick like you do with the label. So, for example, combining the above with

  width: 30ch;

*Should* give you a field that's *at least* 30ch wide, but possibly wider if the label is wider.

(By setting the width on the rather than the whole component, you don’t have to add magic numbers for the chrome around the value)

To make that easy to do through Java, you can use a custom property:

  width: calc(var(--length) * 1ch);

`textField.getStyle().set("--length", "30");`

(I haven’t actually tested this combination btw, but I would expect it to work somewhat decently :grin: )

That CSS block could be applied to multiple field types by listing them in an :is() selector like so:

  width: calc(var(--length) * 1 ch);

It is the label width that is the biggest issue. Testing out your suggestion on DatePicker gives exactly what I want:
`–vaadin-field-default-width: min-content;

vaadin-date-picker::part(label) {
padding-right: 0;

vaadin-date-picker > input {
width: 9ch;

( No idea why I had to use ::part on label and “>” on input… )
( No idea why there is a padding-right. Maybe the Required marker, which we don’t use?)

The “Date of Birth” has a narrow label, and gets the width of the input. The other two get the width of the label.
Nice :slightly_smiling_face:

Forgot the screenshot

Have now tried with css variable as well. Didn’t work out of the box, but when I moved the unit to the variable it worked like a charm:
`textField.getStyle().set(“–length”, “30ch”);

vaadin-text-field > input {
width: var(–length);
That actually gives me more freedom on the server-side, so I think I prefer that anyway. :+1:

Yes, the padding-right on the label container is to make room for the required indicator.
And the label selector is ::part() because it’s inside the component’s shadow DOM, whereas the <input> is > input because it’s not inside the shadow DOM (for accessibility reasons, primarily).

Glad to hear it worked! I’m thinking that we might make this an official feature, one way or another, as it’s a fairly common desire to get this behavior.

Please let me know if you encounter some cases where it doesn’t work, or causes issues!