xryl669
xryl669

Reputation: 3614

Change text alignement upon flex break

I'm writing a form that's supposed to be responsive, that is, when the browser window is small the left label "jumps" on top (see example below by removing the text-align property and resizing the browser).

Right now, it works well when the label text is left aligned. For usuabilities issue, I'd like the label to be right aligned (I don't want them too far from the input box) but as soon as the flex has wrapped, I don't want them to be right aligned anymore.

Example code: https://jsfiddle.net/xhtfqbzL/

.cont {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}
span
{
  flex: 1 0 20vw;
  text-align: right;
  display: inline-block;
  padding-right: 0.5em;
}
input
{
  flex: 1 1 20rem;
  display: inline-block;
}
<div class='cont'>
<span>Label</span>
<input type="text" placeholder="content">
</div>

So how to achieve this effect ?

Upvotes: 3

Views: 994

Answers (2)

xryl669
xryl669

Reputation: 3614

Right now, my best attempt is this:

   html, body { padding: 0; margin: 0 }
   .cont
{
    display: flex;
    flex-wrap: wrap;
    flex-direction: row;
}

.cont span, .cont input
{
    display: inline-block;
}

.cont span
{
    box-sizing: border-box;
    text-align: right;
    background: blue;
    flex: 1 0 20rem;
}
@media screen and (max-width: 40rem) { 
    .cont span { text-align: left; }
}

.cont input
{
    background: green;
    flex: 1 1 50vw;
    box-sizing: border-box;
}
<div class='cont'>
    <span>Some Label</span>
    <input type="text" value="Text here">
</div>

I did not want to have media queries because there's always the pain to find the breaking point (and this can evolve based on the design changes or device evolutions). Here, I've still used media queries but the 40rem magic value is computed, not selected by hand. Typically, one of the form's row item has a fixed size, the other being relative to the viewport size. In this example, the fixed size is 20rem.

Since they are flex items, they'll wrap when they can't fit anymore on this row, that is when their width will reach their flex-basis property.

Thus, the width of the row is span_width[20rem] + input_width[50% * w] = 100% * w I'm deducing that they will wrap when 100% w = 50% w + 20rem => 50%w = 20rem => w = 40rem

So they'll wrap when the viewport's width becomes lower than 40rem.

The main issue with this is that you must know the exact margin & padding size around such items and this is a real pain to maintain.

Another solution which is not using media queries:

   html, body { padding: 0; margin: 0 }
#test
{
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    flex-direction: row;
}
#test p
{
    flex: 0 1 20rem;
    padding:0;
    margin:0;
}
#test p s
{
    width: calc((48vw - 100%) * 5000);
    min-width: 1%;
    max-width: 16rem;
    
    display: inline-block;
    background: salmon;
    height: 1rem;
}
#test p span
{
    background: red;
    padding-right: 0.5rem;
}
#test b
{
    flex: 1 0 50vw;
    background: yellow;
}
<div id="test">
    <p>
        <s></s>
        <span>First</span>
    </p>
    <b>Second</b>
</div>

This works this way:

  1. The label is split in 2 inline elements, a spacer and the label itself.
  2. The spacer is using the Fab Four trick, that is, the width property is calculated to oscillate between +infinite and -infinite in order to be constrained by either min-width and max-width.
  3. The breakpoint is set by the 2nd item in the flex's row (in the example, window_width:100vw - input_width:50vw): when the size left for the label is smaller than the minimum width of the row, its max-width property is used and this adds a "space" push the label element to the right. When the flex row wraps, the size left is now very large and above the breakpoint, and thus the min-width is selected (in my example, I've used 1% but it can be 0%, that is, almost no "margin" on the left of the label).

The caveat of this technic is that you must add a element (could probably be done with a ::before pseudo element here) and you must set a max-width less than the parent width - label width. That point makes this solution is a pain to maintain.

Upvotes: 0

Temani Afif
Temani Afif

Reputation: 273649

Here is a hack to be used with caution (or not used at all ...)

.cont {
  display: flex;
  flex-wrap: wrap;
  overflow: hidden;
}

.cont:before {
  content: "";
  flex: 1 0 20vw;
  height: 1.2em;
}

span {
  flex: 1 1 20rem;
  position: relative;
}

span:before,
span:after {
  content: attr(data-text);
  position: absolute;
  padding-right: 0.5em;
}

span:before {
  left: 0;
  bottom: 100%;
}

span:after {
  top: 0;
  right: 100%;
}

input {
  width: 100%;
}
<div class='cont'>
  <span data-text="Label">
  <input type="text" placeholder="content">
  </span>
</div>

Upvotes: 2

Related Questions