129

Let's say I have a couple of columns, of which some I'd like to rotate the values of:

http://jsfiddle.net/MTyFP/1/

<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:

A series of four block-level elements. The third element's text is rotated.

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:

The third column's text now affects its parent box's height such that the text fits within the box.

Mark Amery
  • 110,735
  • 57
  • 354
  • 402
Chris
  • 8,973
  • 3
  • 26
  • 31
  • 1
    could be helpful http://stackoverflow.com/questions/7565542/width-height-after-transform – Pete Apr 30 '13 at 13:57
  • 8
    writing-mode is now avalaible for most browsers ( use to be an IE5 feature) I would do nowdays http://jsfiddle.net/MTyFP/698/ see: https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode – G-Cyrillus Apr 10 '17 at 14:34
  • 3
    @G-Cyr `writing-mode` is *available* for most browsers, but horribly buggy. Over on a related question, I went through multiple iterations of trying to work around browser-specific bugs before giving up; you can see my succession of failures at https://stackoverflow.com/posts/47857248/revisions, where I start with something that works in Chrome but not Firefox or Edge, then break Chrome in the process of fixing the other two, and end up reverting my answer and labelling it as Chrome-only. Be cautious, if you want to go down this path. (Also note that it's of no use with non-text elements.) – Mark Amery Dec 17 '17 at 22:34
  • 1
    afaik writing-mode only works on text... – Fredrik Johansson May 23 '19 at 14:02

6 Answers6

59

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.

Short answer

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;
}

Stack snippet demo

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>

How does this work?

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:

  1. Somehow construct some other element whose height is equal to the width of the .element-to-rotate
  2. Write our transform on .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:

  1. We set 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.)
  2. We set the 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!

Why isn't this fully spec-compliant?

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.

Mark Amery
  • 110,735
  • 57
  • 354
  • 402
  • 1
    I accepted this as the answer for the moment. The `writing-mode` solutions will be the best approach if IE 11 doesn't need to be supported. – Chris Jun 04 '18 at 06:59
  • 1
    Notable catch: Doesn't work for other rotations like 45deg. – E. Villiger Aug 08 '18 at 10:11
  • although accepted, this is not the up-to-date correct answer. See below for answers using writing-mode. – GilShalit Nov 28 '18 at 16:36
  • @mark-amery, I had to adjust the CSS for my code: https://imgur.com/a/ZgafkgE 1st image with your CSS, 2nd with adjustments I removed transformOrigin: 'top left', & changed transform: 'translate(-100%)', to transform: 'translate(20%)',
    {title}
    – Dan Bitter May 26 '20 at 11:26
58

As the comment by G-Cyr rightfully points out, current support for writing-mode is more than decent. Combined with a simple rotate, you get the exact result that you want. See the example below.

.statusColumn {
  position: relative;
  border: 1px solid #ccc;
  padding: 2px;
  margin: 2px;
  width: 200px;
}

.statusColumn i,
.statusColumn b,
.statusColumn em,
.statusColumn strong {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
  display: inline-block;
  overflow: visible;
}
<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>
Bram Vanroy
  • 22,919
  • 16
  • 101
  • 195
42

Unfortunately (?) this is how it's supposed to work even though you rotate your element it still has certain width and height, that does not change after rotation. You visually change it, but there is no invisible wrapping box that changes its size when you rotate things.

Imagine rotating it less than 90° (e.g. transform: rotate(45deg)): you would have to introduce such invisible box which now has ambiguous dimensions based on the original dimensions of the object you're rotating and the actual rotation value.

Rotated object

Suddenly, you do not only have the width and height of the object you have rotated, but you also have the width and height of the "invisible box" around it. Imagine requesting the outer width of this object - what would it return? The width of the object, or our new box? How would we distinguish between both?

Therefore, there is no CSS that you can write to fix this behavior (or should I say, "automate" it). Of course you can increase the size of your parent container by hand, or write some JavaScript to handle that.

(Just to be clear, you can try using element.getBoundingClientRect to get the rectangle mentioned before).

As described in the spec:

In the HTML namespace, the transform property does not affect the flow of the content surrounding the transformed element.

This means that no changes will be made to the content surrounding the object you're transforming, unless you do them by hand.

The only thing that is taken into account when you transform your object is the overflow area:

(...) the extent of the overflow area takes into account transformed elements. This behavior is similar to what happens when elements are offset via relative positioning.

See this jsfiddle to learn more.

It's actually quite good to compare this situation to an object offset using: position: relative - the surrounding content does not change, even though you're moving your object around (example).


If you want to handle this using JavaScript, have a look at this question.

Lee Taylor
  • 6,091
  • 14
  • 26
  • 43
MMM
  • 6,855
  • 2
  • 22
  • 42
  • 9
    I would say that in a normal UI framework the width and height of your parents should be wrapped around the width and height of the child's bounding box which is affected as you mentioned by the child's rotations. The width and height of the children is their width and height before rotation and the width and height of the parents is defined by the bbox of the children. That's very basic and I'd say CSS/HTML should work that way... If it was no one would ask questions on this topic which can only be fixed in unsatisfactory manners with hacks. – user18490 Oct 14 '17 at 09:38
25

Use percentages for padding and a pseudo element to push the content. In the JSFiddle I left the pseudo element red to show it and you'll have to compensate the shift of the text but I think that's the way to go.

.statusColumn {
    position: relative;
    border: 1px solid #ccc;
    padding: 2px;
    margin: 2px;
    width: 200px;
}

.statusColumn i, .statusColumn b, .statusColumn em, .statusColumn strong {
  writing-mode: tb-rl;
  white-space: nowrap;
  display: inline-block;
  overflow: visible;
  -webkit-transform: rotate(-90deg);
  -moz-transform: rotate(-90deg);
  -ms-transform: rotate(-90deg);
  -o-transform: rotate(-90deg);
  transform: rotate(-90deg);
  -webkit-transform: rotate(-90deg);

  /* also accepts left, right, top, bottom coordinates; not required, but a good idea for styling */
  -webkit-transform-origin: 50% 50%;
  -moz-transform-origin: 50% 50%;
  -ms-transform-origin: 50% 50%;
  -o-transform-origin: 50% 50%;
  transform-origin: 50% 50%;

  /* Should be unset in IE9+ I think. */
  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
}
.statusColumn b:before{ content:''; padding:50% 0; display:block;  background:red; position:relative; top:20px
}
<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>

http://jsfiddle.net/MTyFP/7/

A write-up of this solution can be found here: http://kizu.ru/en/fun/rotated-text/

TylerH
  • 19,065
  • 49
  • 65
  • 86
Litek
  • 4,850
  • 1
  • 21
  • 28
  • 3
    The trouble with this solution is that the element keeps its original width, so in (for example) a table, if it was the widest cell, it would push that column out wide with unnecessary whitespace that *would* have been taken up if the text had not been rotated. That's annoying. – Jez Dec 02 '14 at 15:07
  • @litek, Could you please have a look at http://jsfiddle.net/MTyFP/7/ ? I am having almost same issue with image. Question: http://stackoverflow.com/questions/31072959/image-rotation-design-by-css-and-js/31073210?noredirect=1#comment50200740_31073210 – Awlad Liton Jun 27 '15 at 20:15
11

Please see the updated version in my other answer. This answer is no longer valid and simply does not work any more. I'll leave it here for historical reasons.


This question is quite old, but with the current support for the .getBoundingClientRect() object and its width and height values, in combination with the ability to use this neat method together with transform, I think my solution should be mentioned as well.

See it in action here. (Tested in Chrome 42, FF 33 & 37.)

getBoundingClientRect calculates the actual box width and height of the element. Quite simply, then, we can loop through all elements and set its min-height to the actual box height of their children.

$(".statusColumn").each(function() {
    var $this = $(this),
        child = $this.children(":first");
    $this.css("minHeight", function() {
        return child[0].getBoundingClientRect().height;
    });
});

(With some modification you can loop through the children and find out which one's the tallest and set the height of the parent to the tallest child. However, I decided to make the example more straightforward. You can also add padding of the parent to the height if you want, or you can use a relevant box-sizing value in CSS.)

Note, though, that I added a translate and transform-origin to your CSS to make the positioning more flexible and accurate.

transform: rotate(-90deg) translateX(-100%);
transform-origin: top left;
Bram Vanroy
  • 22,919
  • 16
  • 101
  • 195
  • Really? A css question with an JQUERY answer and you call it a perfect answer @MichaelLumbroso? It works yes, but there is no need for javascript or a whole js library like jQuery – Nebulosar Nov 13 '17 at 19:33
  • 1
    @Nebulosar Nice digging. At the time (already two and a half years ago!) this was the only answer that didn't cause any issues with positioning or pseudo elements not working well. Luckily, browser support for some properties has improved. Please see my edit. – Bram Vanroy Nov 14 '17 at 19:13
  • 2
    This is essentially two completely unrelated answers in one. It's perfectly within the rules to post multiple answers to one question; I think it would've been preferable if your new solution added in 2017 had been a new answer, so that the two completely separate answers could be voted and commented on separately. – Mark Amery Dec 17 '17 at 22:28
  • @MarkAmery You are right. I edited the question and added a new answer [here](https://stackoverflow.com/a/50406895/1150683). – Bram Vanroy May 18 '18 at 08:24
  • I was able to use this answer and the image load event to get a working solution for images using: $pic.on('load', function () { $pic.css('min-height', $pic[0].getBoundingClientRect().height); }); – Miika L. Apr 12 '19 at 06:15
-20

I know it's an old post but I found it while struggling with exactly the same problem. The solution that works for me is rather crude "low-tech" method by simply surround the div I rotate by 90deg with a lot of

<br>

Knowing the approximate width (which becomes height after rotation) of div I can compensate the difference by adding br's around this div so content above and below gets pushed accordingly.

vic_sk
  • 35
  • 8
  • 13
    Maybe this works but you should never be doing this. – MMM Jul 25 '14 at 15:20
  • Thanks for your feedback! Yes, a better way I use div's margin-top property to adjust the height in pixels when object is rotated. – vic_sk Aug 12 '14 at 21:16