Search code examples
htmlcsshtml-tablewidthcol

table column width by content but not more the 50%


Good day. I have table with 2 columns: the first one for labels and the second one for control elements. Labels could be short and very long.

table{
padding-bottom: 20px;
}

td{ border: 1px solid}
<table style="width:100%;">
    <tr>
      <td class="first-column">very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very label</td>
      <td class="second-column"><input style="width:100%"/></td>
    </tr>
    <tr>
      <td class="first-column">label</td>
      <td class="second-column"><input style="width:100%"/></td>
    </tr>
</table>
    
  
<table style="width:100%;">
    <tr>
      <td class="first-column">medium label</td>
      <td class="second-column"><input style="width:100%"/></td>
    </tr>
    <tr>
      <td class="first-column">label</td>
      <td class="second-column"><input style="width:100%"/></td>
    </tr>
 </table>

 <table style="width:100%;">
    <tr>
      <td class="first-column">label</td>
      <td class="second-column"><input style="width:100%"/></td>
    </tr>
    <tr>
      <td class="first-column">label</td>
      <td class="second-column"><input style="width:100%"/></td>
    </tr>
 </table>

I need the first column to be sized by it's content (width of the longest label), but in case of very long labels it should not fill more then 50% of table. Something like this enter image description here

Could you please help me to write correct css for this case? I can't use Javascript, it should be css-only solution.


Solution

  • My own suggestion for this would be to firstly – unless this is impractical for other reasons – consolidate all your label and <input> elements into the same parent-element, which simplifies the alignment of all columns.

    To this end I've grouped the elements into a single <fieldset> element, itself within a <form>:

    // This JavaScript has no part to play in the resizing of elements,
    // but merely logs the size of each of the two columns in the
    // relevant elements (the last <label> and the last <input>:
    const D = document,
                logLengths = () => {
        let label = D.querySelector('label:last-of-type'),
            input = D.querySelector('input:last-of-type');
      [label, input].forEach(
        (el) =>{
            el[ 
            el.tagName.toLowerCase() === 'input' ? 'value' : 'textContent'
            ] = Math.round(el.getBoundingClientRect().width*10)/10 + 'px';
        });
    };
    
    logLengths();
    D.querySelectorAll('label').forEach(
        (label) => label.addEventListener('input', logLengths)
    );
    /* Generic CSS reset: */
    *,
    ::before,
    ::after {
      box-sizing: border-box;
      font-size: 16px;
      line-height: 1.5;
      margin: 0;
      padding: 0;
    }
    
    form {
      border: 1px solid currentColor;
      /* using CSS logical properties to place a 1em margin
         on the block axis (top/bottom) in left-to-right, top-
         to-bottom languages) */
      margin-block: 1em;
      /* ...and a margin of auto (to centre the element) on
         the inline-axis (left and right in ltr languages): */
      margin-inline: auto;
      padding: 0.2em;
      width: 90vw;
    }
    
    fieldset {
      /* in order to use CSS Grid: */
      display: grid;
      /* setting gaps/'gutters' between rows of 0.5em,
         and 0.25em gaps between adjacent columns: */
      gap: 0.5em 0.25em;
      /* defining two columns, the first of which is sized
         'auto', allowing the layout to be sized appropriately
         to the content within the limits of other columns,
         the second column is sized using the 'minmax()'
         function between a minimum size of 50% (because the
         maximum permitted size of the first column is 50%),
         and 1fr, which is the fractional unit of the remaining
         space available: */
      grid-template-columns: auto minmax(50%, 1fr);
    }
    
    /* this styles the 'information' <div> that provides
       guidance about the interactivity of the editable
       <label> elements: */
    fieldset div {
      grid-column: 1 / -1;
      text-align: center;
      padding: 0 2rem;
    }
    
    code, kbd {
      font-family: consolas, ubuntu mono, monospace;
    }
    
    kbd {
      border: 1px solid #aaa;
      border-radius: 0.4em;
      padding: 0.1em 0.3em;
    }
    
    label {
      /* to indicate interactivity: */
      cursor: pointer;
      /* for positioning the pseudo element: */
      position: relative;
    }
    
    /* entirely irrelevant to the demo, but just to
       make it look a little prettier (adjust to
       taste or remove as you like): */
    label::before {
      content: '';
      background: linear-gradient(90deg, lime, #ffff);
      height: 100%;
      position: absolute;
      transform: scaleX(0);
      transition: transform 0.3s ease-in-out;
      transform-origin: 0 100%;
      width: 100%;
      z-index: -1;
    }
    
    label:hover::before {
      transform: scaleX(1);
    }
    
    .length {
      background: revert;
      background-color: #fff;
      border-color: transparent;
      border-top: 1px solid currentColor;
      cursor: not-allowed;
      font-style: italic;
      text-align: center;
    }
    <form action="#">
      <fieldset>
      <!-- this <div> is here simply to provide some guidance as to the
           interactivity of the <label> elements for the purpose of this
           demo; otherwise it can be removed entirely: -->
      <div>Most labels are <code>contentEditable</code>, though they will focus the &lt;input&gt; element when clicked; <kbd>shift</kbd>&plus;<kbd>tab</kbd> to focus the &lt;label&gt;</div>
        <!-- because we're using a grid (and the majority of browsers don't yet
             support display: subgrid) to create the aligned columns, the <input>
             elements are the following-siblings of the <label> elements; in order
             to associate the <label> with the correct <input> I've added an 'id'
             attribute to each <input>, and the same id-value for the associated
             <label> in their 'for' attribute; this means clicking/tapping on a
             <label> will focus the appropriate <input>: -->
        <label for="inputElement_0" contentEditable>longer label</label>
        <input id="inputElement_0">
        <label for="inputElement_1" contentEditable>label</label>
        <input id="inputElement_1">
        <label for="inputElement_2" contentEditable>medium label</label>
        <input id="inputElement_2">
        <label for="inputElement_3" contentEditable>label</label>
        <input id="inputElement_3">
        <label for="inputElement_4" contentEditable>label</label>
        <input id="inputElement_4">
        <label for="inputElement_5" contentEditable>label</label>
        <input id="inputElement_5">
        <!-- these elements are here purely to report the length of the 
             grid-columns in which they appear: -->
        <label for="lengthInput" class="length"></label>
        <input id="lengthInput" type="text" class="length" readonly>
      </fieldset>
    </form>

    JS Fiddle demo.

    If subgrid is available in your browser (currently it's only available in Firefox), then the following is possible:

    // Again, this JavaScript has nothing to do with the resizing, but only shows
    // the widths of the grid-tracks the elements are in:
    const D = document,
      logLengths = () => {
        let label = D.querySelector('label:last-of-type'),
          input = label.querySelector('input:last-of-type');
        [label, input].forEach(
          (el) => {
            let width = Math.round(el.getBoundingClientRect().width / 10) * 10,
              val = `${width}px`;
            if (el.matches('label')) {
              label.firstChild.nodeValue = val;
            } else if (el.matches('input')) {
              el.value = val;
            }
          });
      };
    
    logLengths();
    D.querySelectorAll('label').forEach(
      (label) => label.addEventListener('input', logLengths)
    );
    *,
    ::before,
    ::after {
      box-sizing: border-box;
      font-size: 16px;
      line-height: 1.5;
      margin: 0;
      padding: 0;
    }
    
    form {
      border: 1px solid currentColor;
      margin-block: 1em;
      margin-inline: auto;
      padding: 0.2em;
      width: 90vw;
    }
    
    fieldset {
      display: grid;
      gap: 0.5em 0.25em;
      grid-template-columns: auto minmax(50%, 1fr);
    }
    
    fieldset div {
      grid-column: 1 / -1;
      text-align: center;
      padding: 0 2rem;
    }
    
    code,
    kbd {
      font-family: consolas, ubuntu mono, monospace;
    }
    
    kbd {
      border: 1px solid #aaa;
      border-radius: 0.4em;
      padding: 0.1em 0.3em;
    }
    
    label {
      cursor: pointer;
      position: relative;
      /* Using display: grid to allow us to make use
         of subgrid: */
      display: grid;
      /* instructing the layout engine to use the
         grid-columns/grid-tracks of the parent
         element: */
      grid-template-columns: subgrid;
      /* positioning the grid-start in the first
         grid-column and the grid-end in the last
         grid-column: */
      grid-column: 1 / -1;
    }
    
    label::before {
      content: '';
      background: linear-gradient(90deg, lime, #ffff);
      height: 100%;
      position: absolute;
      transform: scaleX(0);
      transition: transform 0.3s ease-in-out;
      transform-origin: 0 100%;
      width: 100%;
      z-index: -1;
    }
    
    label:hover::before {
      transform: scaleX(1);
    }
    
    .length {
      background: revert;
      background-color: #fff;
      border-color: transparent;
      border-top: 1px solid currentColor;
      cursor: not-allowed;
      font-style: italic;
      text-align: center;
    }
    <form action="#">
      <fieldset>
        <div>All labels are <code>contentEditable</code>, though they will focus the &lt;input&gt; element when clicked; <kbd>shift</kbd>&plus;<kbd>tab</kbd> to focus the &lt;label&gt;</div>
        <!-- because we can use subgrid, we can place the <input> elements
             inside of their associated <label> elements, which removes the
             need to assign either a 'for' attribute to the <label> or an
             'id' attribute to the <input>, since they're automatically
             associated this way via nesting: -->
        <label contentEditable>longer label
          <input>
        </label>
    
        <label contentEditable>label
          <input>
        </label>
        <label contentEditable>medium label
          <input>
        </label>
        <label contentEditable>label
          <input>
        </label>
        <label contentEditable>label
          <input>
        </label>
        <label contentEditable>label
          <input>
        </label>
        <label class="length">
          <input type="text" class="length" readonly>
        </label>
      </fieldset>
    </form>

    JS Fiddle demo.

    References: