Let's say I have a couple of columns, of which some I'd like to rotate the values of:
<div class="container">
<div class="statusColumn"><span>Normal</span></div>
<div class="statusColumn"><a>Normal</a></div>
<div class="statusColumn"><b>Rotated</b></div>
<div class="statusColumn"><abbr>Normal</abbr></div>
</div>
With this CSS:
.statusColumn b {
writing-mode: tb-rl;
white-space: nowrap;
display: inline-block;
overflow: visible;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
It ends up looking like this:
Is it possible to write any CSS that will cause the rotated element to affect its parent's height, such that the text would not overlap the other elements? Something like this:
Assuming that you want to rotate 90 degrees, this is possible, even for non-text elements - but like many interesting things in CSS, it requires a little cunning. My solution also technically invokes undefined behaviour according to the CSS 2 spec - so while I've tested and confirmed that it works in Chrome, Firefox, Safari, and Edge, I can't promise you that it won't break in a future browser release.
Given HTML like this, where you want to rotate .element-to-rotate
...
<div id="container">
<something class="element-to-rotate">bla bla bla</something>
</div>
... introduce two wrapper elements around the element that you want to rotate:
<div id="container">
<div class="rotation-wrapper-outer">
<div class="rotation-wrapper-inner">
<something class="element-to-rotate">bla bla bla</something>
</div>
</div>
</div>
... and then use the following CSS, to rotate anti-clockwise (or see the commented out transform
for a way to change it into a clockwise rotation):
.rotation-wrapper-outer {
display: table;
}
.rotation-wrapper-inner {
padding: 50% 0;
height: 0;
}
.element-to-rotate {
display: block;
transform-origin: top left;
/* Note: for a CLOCKWISE rotation, use the commented-out
transform instead of this one. */
transform: rotate(-90deg) translate(-100%);
/* transform: rotate(90deg) translate(0, -100%); */
margin-top: -50%;
/* Not vital, but possibly a good idea if the element you're rotating contains
text and you want a single long vertical line of text and the pre-rotation
width of your element is small enough that the text wraps: */
white-space: nowrap;
}
p {
/* Tweak the visuals of the paragraphs for easier visualiation: */
background: pink;
margin: 1px 0;
border: 1px solid black;
}
.rotation-wrapper-outer {
display: table;
}
.rotation-wrapper-inner {
padding: 50% 0;
height: 0;
}
.element-to-rotate {
display: block;
transform-origin: top left;
/* Note: for a CLOCKWISE rotation, use the commented-out
transform instead of this one. */
transform: rotate(-90deg) translate(-100%);
/* transform: rotate(90deg) translate(0, -100%); */
margin-top: -50%;
/* Not vital, but possibly a good idea if the element you're rotating contains
text and you want a single long vertical line of text and the pre-rotation
width of your element is small enough that the text wraps: */
white-space: nowrap;
}
<div id="container">
<p>Some text</p>
<p>More text</p>
<div class="rotation-wrapper-outer">
<div class="rotation-wrapper-inner">
<p class="element-to-rotate">Some rotated text</p>
</div>
</div>
<p>Even more text</p>
<img src="https://i.stack.imgur.com/ih8Fj.png">
<div class="rotation-wrapper-outer">
<div class="rotation-wrapper-inner">
<img class="element-to-rotate" src="https://i.stack.imgur.com/ih8Fj.png">
</div>
</div>
<img src="https://i.stack.imgur.com/ih8Fj.png">
</div>
Confusion in the face of the incantations I've used above is reasonable; there's a lot going on, and the overall strategy is not straightforward and requires some knowledge of CSS trivia to understand. Let's go through it step by step.
The core of the problem we face is that transformations applied to an element using its transform
CSS property all happen after layout has taken place. In other words, using transform
on an element does not affect the size or position of its parent or of any other elements, at all. There is absolutely no way to change this fact of how transform
works. Thus, in order to create the effect of rotating an element and having its parent's height respect the rotation, we need to do the following things:
.element-to-rotate
.element-to-rotate
such as to overlay it exactly on the element from step 1.The element from step 1 shall be .rotation-wrapper-outer
. But how can we cause its height to be equal to .element-to-rotate
's width?
The key component in our strategy here is the padding: 50% 0
on .rotation-wrapper-inner
. This exploit's an eccentric detail of the spec for padding
: that percentage paddings, even for padding-top
and padding-bottom
, are always defined as percentages of the width of the element's container. This enables us to perform the following two-step trick:
display: table
on .rotation-wrapper-outer
. This causes it to have shrink-to-fit width, which means that its width will be set based upon the intrinsic width of its contents - that is, based upon the intrinsic width of .element-to-rotate
. (On supporting browsers, we could achieve this more cleanly with width: max-content
, but as of December 2017, max-content
is still not supported in Edge.)height
of .rotation-wrapper-inner
to 0, then set its padding to 50% 0
(that is, 50% top and 50% bottom). This causes it to take up vertical space equal to 100% of the width of its parent - which, through the trick in step 1, is equal to the width of .element-to-rotate
.Next, all that remains is to perform the actual rotation and positioning of the child element. Naturally, transform: rotate(-90deg)
does the rotation; we use transform-origin: top left;
to cause the rotation to happen around the top-left corner of the rotated element, which makes the subsequent translation easier to reason about, since it leaves the rotated element directly above where it would otherwise have been drawn. We can then use translate(-100%)
to drag the element downwards by a distance equal to its pre-rotation width.
That still doesn't quite get the positioning right, because we still need to offset for the 50% top padding on .rotation-wrapper-outer
. We achieve that by ensuring that .element-to-rotate
has display: block
(so that margins will work properly on it) and then applying a -50% margin-top
- note that percentage margins are also defined relative to the width of the parent element.
And that's it!
Because of the following note from the definition of percentage paddings and margins in the spec (bolding mine):
The percentage is calculated with respect to the width of the generated box's containing block, even for 'padding-top' and 'padding-bottom'. If the containing block's width depends on this element, then the resulting layout is undefined in CSS 2.1.
Since the entire trick revolved around making the padding of the inner wrapper element be relative to the width of its container which was in turn dependent upon the width of its child, we're hitting this condition and invoking undefined behaviour. It currently works in all 4 major browsers, though - unlike some seemingly spec-compliant tweaks to approach that I've tried, like changing .rotation-wrapper-inner
to be a sibling of .element-to-rotate
instead of a parent.