Separating Internal and External Styles
Tags: css
Published: 2022-06-03 00:05:31

_**TL;DR:** An element should not care about it's size and position within its parent and therefore should never set its own size or position properties._

I've been following this mantra over several projects and have found it to be a great guideline for building more versatile components and keeping CSS code more structured. To be honest, I don't think I came up with it, yet I'm not sure where I first heard about it and since I rarely see it used in the wild, I want to share it with you now.

## The basic concept
- I divide CSS properties into two categories... _internal_ and _external_. 
- Internal CSS properties affect the styling of the element itself.  
- External properties affect the positioning and sizing within a layout. 
- As a general rule-of-thumb everything affecting the border inward is _internal_ and everything from the margin outward is _external_. 
- **An element should avoid setting _external_ properties on itself. External properties should only be set from the context of its containing parent element.**

This helps in a few ways:
- Child elements / components become less context sensitive and therefore are more reusable.
- The code for positioning a child component is always in a predictable place located near the code for the layout.
- It leads to fewer properties for sizing being set which allows more automatic sizing by the browser and fewer cases where a parent must override a property on a child.
- Indirectly, it implies that components should have rules outside of the context of their parents which will lead to fewer times when overly specific selectors prevent overrides or require refactoring.

Some examples of "Internal" properties:
- `font-size`
- `background-color`
- `padding` (affects spacing _inside_ the border-box)
- `grid-template-columns` (affects the shape of the grid)

Some examples of "External" properties:
- `width` & `height` (affects elment's size)
- `top` & `left` (affects element's position)
- `margin` (affects distance between other elements _outside_ the border-box)
- `grid-column-start` (affects the element's position in the grid)

Here's a quick example of a container setting external properties on a child. Check out the SCSS tab.

{% codepen https://codepen.io/mimshwright/pen/LezYad %}

## Classifying properties as Internal or External

CSS is so complex and laden with historical baggage that it’s difficult to make a clear distinction between internal and external in every case. The philosophy behind the approach is clear though, make components as flexible as possible by making few assumptions about the context they will be used in.

Here I will do my best to make a mostly-comprehensive list of all external and internal properties (and weird in-between properties) and explain how I came to the conclusion I did.

## External properties
- **Positioning properties**
  - `bottom`
  - `left`
  - `top`
  - `right`
- **Sizing properties**
  - `width` (and `min-`, `max-`)
  - `height` (and `min-`, `max-`)
- **Floated layout properties** – _Side note: with modern browsers and CSS, you should probably avoid using these entirely in favor of grids and flex._
  - `float`
  - `clear`
- **Table cell sizing properties**
  - `column-span`
  - `row-span`
- **Flex-box child properties**
  - `flex`
  - `flex-basis`
  - `flex-grow`
  - `flex-shrink`
  - `align-self`
  - `justify-self`
  - `order`
- **Grid child properties** – _Side note: this [Complete Guide to Grid](https://css-tricks.com/snippets/css/complete-guide-grid/) even separates grid properties clearly into children and container._
  - `grid-area`
  - `grid-column` (and `-start`, `-end`)
  - `grid-row` (and `-start`, `-end`)
- **Other properties**
  - `margin` (including `-top`, `-right`, `-bottom`, `-left`)
  - `box-sizing`  — _Side note: If you’re not already, I would recommend [setting `box-sizing: border-box` globally](https://www.abeautifulsite.net/posts/box-sizing-border-box-explained/)._
  - `resize`
  - `z-index`

## Special cases / Grey areas
These properties are more difficult to categorize.

### `position`
`position` seems obvious as an external property. It is in fact all about how the element would be positioned in a layout. However, when `position` is set to `static` (the default value) this has a big effect on the interior, namely the child elements within ignore the statically positioned container and seek the next positioned (non-static) ancestor. Statically-positioned elements also ignore the `top`, `right`, `bottom`, `left`, and `z-index` properties. As a result, there are many times where you would need to set this on a container to affect its children.

#### Recommendation:
Always set a container’s `position` to `relative` if it has positioned children. If it needs to be `absolute`, `fixed` or `sticky`, set that in its parent which should override the relative value. The goal is to try to avoid the confusing issue of children that are positioned relative to a distant ancestor by always positioning elements relative to their direct parent.

### `display`
`display` is one of the trickiest because it affects both the internal and external. In fact, the [CSS3 spec](https://drafts.csswg.org/css-display/#the-display-properties) even specifies that there are categories of `display` values including `<display-inside>` and `<display-outside>` but support for setting them independently is minimal.

`flex`, `table` or `grid` are internal components of display. They affect how the elements within it are laid out.

`inline` and `block` and their variants are external since they change an element's layout within its parent. Likewise, `none` is external.

What if you want to set the outside `display` to `inline` but the `inside` display to `flex`? Well, you can use [`inline-flex`](https://codepen.io/mimshwright/pen/rYGrgg) but it requires some awareness to avoid overriding the child element's display.

Luckily, this is not a very common issue.

#### Recommendation:
Treat as internal except when changing so-called outside values, `inline`, `block`, `none`, etc. Be sure to respect the inside value (e.g. check to make sure you’re not removing `flex` from an inline child).

### `transform`
`transform` does not change the block box drawn for layout purposes. However, it could certainly be used as an external property. You’ll have to determine what makes most since for your project - sometimes you'll use `transform` to change the position or sizing within a layout while sometimes it’s more of an internal effect.

#### Recommendation:
If every instance should be transformed (e.g. you need to rotate a square to make a diamond), treat it as internal. If you’re using `transform` to position things in the layout or change an individual instance, or if you’re not sure, treat it as external.

### `box-shadow` & `outline`
`box-shadow` and `outline` both appear visually outside of the border-box. However, like transform, they do not affect the size or position of the element. And furthermore, intuitively they just don’t feel like external properties to me.

#### Recommendation:
Treat these as internal.

### Pseudo-classes dealing with order of children such as `:nth-child`
While not technically properties, I want to mention these pseudo-classes (`:first-child`, `:last-child`, `:only-child`, etc.) since they essentially only would be applied when multiple children are included in a container. Because of that, they should be set by the container.

#### Recommendation:
Treat as external.

### `width` and `height`
🤬 _WTH, dude!? Isn't your whole article about how you shouldn't set the sizing and positioning internally!?_

Yes. I'm sorry. I only want to mention that I have cheated on this one especially when it comes to matching the specific size of, for example, an image. Sometimes the element only makes with a particular fixed size. However, this happens less often than you might think. Always consider how a fixed size might restrict unexpectedly long content or restrict your ability to add the item to a list or grid. Let the container handle the sizing whenever possible.

#### Recommendation:
Treat as external with one exception: Elements that only ever exist as a specific size, or that would look broken without it, could have `width` and `height` set internally. 

## Internal properties
Everything else not mentioned above is probably an internal property, or at least doesn’t make sense as an external property.

## What about...
There are always some cases where this kind of classification breaks down. For example, you need to set the `width` and `height` of an image to an exact pixel value everywhere. It's okay, go ahead and set the `width` and `height`. It's not a big deal. These are only guidelines and you should always make a decision that works best for your special case.

## Conclusion
Whether you call them internal / external, child / container,  skin / layout, or whatever makes sense for you, the idea is the same. An element should not care about its own size and position within its parent and therefore should never set its own size or position properties. That's it. It's a simple enough concept to grasp and yet (as I've discovered) surprisingly difficult to formalize.

Did I miss some external properties? Has someone else already covered this in depth elsewhere? Would you use this technique? What would you change? Please let me know what you think!