Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a two-column layout of with right aligned labels and left aligned values in CSS

Tags:

html

css

All,

What is the simplest way to create a simple layout that looks like this, in HTML and CSS:

enter image description here

Specifically - a right-aligned label on the left, and a left-aligned value on the right.

I don't want to hard-code the width of the left-column - that should be determined based on the width of the longest label.

I'm looking for an approach that's at least reasonably semantic, works well with screen-readers (e.g., screen reader should read the label, and then the value, not all the labels, then all the values), and doesn't require a whole bunch of additional <div> elements.

This seems like a reasonably common layout, so I'm assuming there's a very easy way to do it. But I've yet to figure that out myself.

A <table> would work perfectly, but as everyone reminds me, never use a <table> just for layout. And this is clearly not tabular data.

Thanks in advance!

like image 619
mattstuehler Avatar asked Feb 05 '19 00:02

mattstuehler


People also ask

How do I align a label to the right in CSS?

Solutions with CSS properties We specify the margin-bottom of our <div> element. Then, we set the display of the <label> element to "inline-block" and give a fixed width. After that, set the text-align property to "right", and the labels will be aligned with the inputs on the right side.


2 Answers

There are a couple of options, using minimal HTML; one using CSS Grid and the other using CSS flex-box layout.

CSS Grid:

/* A simple reset to ensure that all elements and
   pseudo-elements have their margin and padding
   set to zero, and all are using the same box-sizing: */
*,
 ::before,
 ::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}


/* here we set the <form> element's layout to use
   grid layout: */
form {
  display: grid;
  /* we use the repeat() function to create 2 columns,
   each column sized with a minimum of 0 width and a
   maximum of 1fr; the 'fr' unit is a fractional unit
   and here forces each column to take one fractional
   unit of the available space to create two equal-sized
   columns: */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* we use the 'gap' property (formerly 'grid-gap') to
   specify the gap between grid elements; here we have
   0.5em above and below and 1em to the left and right: */
  gap: 0.5em 1em;
  width: 80vw;
  margin: 0 auto;
}

label {
  /* aligning the text to the right of the <label> element: */
  text-align: right;
}

label::after {
  /* using the 'content' property of the pseudo-element to
   add the colon character: */
  content: ':'
}
<form>
  <!-- using the 'for' attribute to associate the label with the
       relevant <input> element; the value of the 'for' attribute
       must be equal to the 'id' attribute-value of the relevant
       <input> -->
  <label for="input1">label</label>
  <input type="text" id="input1" placeholder="input 1">
  <label for="input2">A longer label</label>
  <input type="text" id="input2" placeholder="input 2">
  <label for="input3">Another slightly longer label</label>
  <input type="text" id="input3" placeholder="input 3">
  <label for="input4">A label that goes on, frankly, for quite a bit further than might be common for the average &lt;label&gt; element</label>
  <input type="text" id="input4" placeholder="input 4">
</form>

JS Fiddle demo.

Flexbox:

/* A simple reset to ensure that all elements and
   pseudo-elements have their margin and padding
   set to zero, and all are using the same box-sizing: */
*,
 ::before,
 ::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* setting the layout of the <form> to flexbox: */
form {
  display: flex;
/* allowing the child elements of the <form> to wrap
   to new lines when necessary: */
  flex-wrap: wrap;
  width: 80vw;
  margin: 0 auto;
}

/* Setting common properties for the <label> and <input>
   elements: */
label,
input {
/* assigning the flex-grow and flex-shrink (respectively)
   properties to 1, in order that they grow/shrink by the
   same amount relative to each other; and setting the
   flex-basis to 40% (the percentage derived from the parent)
   in order to assign a width that's too large to accommodate
   more than two elements per line: */
  flex: 1 1 40%;
/* setting the margin above/below each 'row' to be 0.5em,
   and 0 to the left and right: */
  margin: 0.5em 0;
}

label {
  text-align: right;
/* setting the margin-right of the <label> to 1em to enforce a
   gutter between the <label> and the neighbouring <input> (the
   CSS Box Alignment module (level 3) introduces the 'gap' property
   that can also be used in the flexbox layout (among others) but
   that's not yet supported by browsers, so we have to use margins: */
  margin-right: 1em;
}

label::after {
  content: ':'
}
<form>
  <label for="input1">label</label>
  <input type="text" id="input1" placeholder="input 1">
  <label for="input2">A longer label</label>
  <input type="text" id="input2" placeholder="input 2">
  <label for="input3">Another slightly longer label</label>
  <input type="text" id="input3" placeholder="input 3">
  <label for="input4">A label that goes on, frankly, for quite a bit further than might be common for the average &lt;label&gt; element</label>
  <input type="text" id="input4" placeholder="input 4">
</form>

JS Fiddle demo.

Now, with both of the above approaches there is one requirement that may not be appropriate for your use-case: the <label> element must implement a for attribute, which requires that the associated <input> must also have an id attribute (and those values must be equivalent).

To avoid that, we could instead nest the <input> within the <label>, which automatically associates the <label> and <input>; this would give (potentially) cleaner HTML:

Grid, again:

*,
::before,
::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5em 1em;
  width: 80vw;
  margin: 0 auto;
}

label {
/* it's a crude simplification, but effectively we remove
   the <label> from the DOM and instead show its contents;
   the text portion, and the <input>, become independant
   grid-items (sort of): */
  display: contents;
}
<form>
  <label>label
    <input type="text" placeholder="input 1"></label>
  <label>A longer label
    <input type="text" placeholder="input 2"></label>
  <label>Another slightly longer label
    <input type="text" placeholder="input 3"></label>
  <label>A label that goes on, frankly, for quite a bit further than might be common for the average label element
    <input type="text" placeholder="input 4"></label>
</form>

JS Fiddle demo.

In the above snippet you may notice that:

  1. we no longer use label::after to insert the presentational ':' characters, since that would – obviously – be placed after the <label> content, following the <input> and would create another grid-item (demo), and
  2. the <label> text is no longer right-aligned; this is because text-align doesn't seem to work on the text.

*,
::before,
::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5em 1em;
  width: 80vw;
  margin: 0 auto;
}

label {
  display: contents;
  text-align: right;
}
<form>
  <label>label
    <input type="text" placeholder="input 1"></label>
  <label>A longer label
    <input type="text" placeholder="input 2"></label>
  <label>Another slightly longer label
    <input type="text" placeholder="input 3"></label>
  <label>A label that goes on, frankly, for quite a bit further than might be common for the average label element
    <input type="text" placeholder="input 4"></label>
</form>

JS Fiddle demo.

We could improve things, visually, be wrapping the text-content of the <label> in an element, such as a <span> to restore that presentation:

*,
::before,
::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5em 1em;
  width: 80vw;
  margin: 0 auto;
}

label {
  display: contents;
  text-align: right;
}

label span::after {
  content: ':';
}
<form>
  <label><span>label</span>
    <input type="text" placeholder="input 1"></label>
  <label><span>A longer label</span>
    <input type="text" placeholder="input 2"></label>
  <label><span>Another slightly longer label</span>
    <input type="text" placeholder="input 3"></label>
  <label><span>A label that goes on, frankly, for quite a bit further than might be common for the average label element</span>
    <input type="text" placeholder="input 4"></label>
</form>

It's also worth noting that both CSS Grid and CSS Flexbox allow us to reorder elements visually in the browser differently than they appear in the DOM; this can allow us to style the <label> elements based on the state of the associated <input>, for example,

Grid:

*,
::before,
::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

form {
  display: grid;
  /* to ensure that the layout backfills the empty spaces
     created by moving content around: */
  grid-auto-flow: dense;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5em 1em;
  width: 80vw;
  margin: 0 auto;
}

label {
  display: contents;
  text-align: right;
}

input {
  /* positioning the <input> elements in the secpond
     grid-track/column: */
  grid-column: 2;
}

label span {
  /* positioning the <span> elements in the first
     grid-track/column: */
  grid-column: 1;
}

label span::after {
  content: ':';
}

input:placeholder-shown+span {
  color: rebeccapurple;
}

input:not(:placeholder-shown)+span {
  color: limegreen;
  font-weight: bold;
}

input:focus+span,
input:active+span {
  color: #f90;
}
<form>
  <label>
    <input type="text" placeholder="input 1">
    <span>label</span>
  </label>
  <label>
    <input type="text" placeholder="input 2">
    <span>A longer label</span>
  </label>
  <label>
    <input type="text" placeholder="input 3">
    <span>Another slightly longer label</span>
  </label>
  <label>
    <input type="text" placeholder="input 4">
    <span>A label that goes on, frankly, for quite a bit further than might be common for the average label element</span>
  </label>
</form>

JS Fiddle demo.

Flexbox:

*,
::before,
::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

form {
  width: 80vw;
  margin: 0 auto;
}

label {
  display: flex;
  margin: 0.5em 0;
}

label span,
label input {
  flex: 1 1 50%;
}

label span {
  order: 1;
  text-align: right;
  margin-right: 1em;
}

label input {
  order: 2;
}

label span::after {
  content: ':';
}

label span::after {
  content: ':';
}

input:placeholder-shown+span {
  color: rebeccapurple;
}

input:not(:placeholder-shown)+span {
  color: limegreen;
  font-weight: bold;
}

input:focus+span,
input:active+span {
  color: #f90;
}
<form>
  <label>
    <input type="text" placeholder="input 1">
    <span>label</span>
  </label>
  <label>
    <input type="text" placeholder="input 2">
    <span>A longer label</span>
  </label>
  <label>
    <input type="text" placeholder="input 3">
    <span>Another slightly longer label</span>
  </label>
  <label>
    <input type="text" placeholder="input 4">
    <span>A label that goes on, frankly, for quite a bit further than might be common for the average label element</span>
  </label>
</form>

JS Fiddle demo.

It's worth noting, though, that grid-auto-flow: dense, as well as rearranging the visual presentation of elements, can cause problems for those users of the web using screen readers or keyboard interaction. So, while it enables some prettiness, it's worth considering if that prettiness comes at the cost of usability (and potential legally-required accessibility).

Further, in the latter examples, the use of a nested <span> element to allow for styling of the text-content of the <label> element (whether for alignment reasons, adding the ':' characters or styling in response to interaction with the <input> elements, this may be unnecessary complication of a potentially 'clean' HTML markup.

References:

  • Selectors:
    • :active.
    • ::after.
    • :focus.
    • :not().
    • :placeholder-shown.
  • Properties
    • box-sizing.
    • display.
    • flex.
    • flex-basis.
    • flex-grow.
    • flex-shrink.
    • gap.
    • grid-auto-flow.
    • grid-column.
    • grid-template-columns.
    • minmax().
    • order.
    • repeat().
  • Property-values:
    • <length> (units).

Bibliography:

  • "A Complete Guide to Grid."
  • "A Complete Guide to Flexbox."
  • "Basic Concepts of Flexbox."
  • "Basic Concepts of Grid Layout."
like image 62
David Thomas Avatar answered Oct 24 '22 07:10

David Thomas


CSS tables to the rescue : https://css-tricks.com/almanac/properties/d/display/

These aren't tables, but can be used to recreate table behavior.

.notATable {
  display:table;
  list-style:none;
  padding-left:0;
}

.notATable > li 
 {
  display:table-row;
 }
 
 .notATable > li > *
 {
  display:table-cell;
  padding:5px;
 }
 
 
 .notATable label {
  font-weight:bold;
  text-align:right;
 }
 
 .notATable label:after {
  content: ':';
 }
<ul class="notATable">
   <li><label>Label</label><div>Value</div></li>
   <li><label>Another Label</label><div>Some Value</div></li>
   <li><label>A longer Label</label><div>Another Value</div></li>
   <li><label>Short Label</label><div>A different Value</div></li>
   <li><label>Really, Really Long Label</label><div>Last Value</div></li>
</ul>

Now you can use CSS to change the layout of your markup, for example you could use media-queries to display your labels and values differently on mobile devices.

like image 4
Jon P Avatar answered Oct 24 '22 07:10

Jon P