From GitLab 12.2 on, we are moving components styles into GitLab UI, as
described in the approved RFC #2,
with the substitution of mixins/@include
for @extend
.
This approach will make it possible to use GitLab UI components in several projects.
You should use utility mixins to implement components’ styles. See
utility-mixins
for a comprehensive list of the available utilities. If what you are
looking for is not available, add it following the naming conventions
indicated in the appropriate file.
When running yarn generate:component
, the component's SCSS file will be created in the right place
and imported in the main components.scss
. Read more about
adding new components.
Each component's stylesheet should contain its "modularized" style:
// src/components/base/button/button.scss
.gl-button {
// style
@include some-utility-mixin;
}
When the component is integrated
into the application, you should still follow the
utility-first
approach for basic layout and other styles.
See GlToken
's stylesheet
for an example of how we use utility mixins in GitLab UI.
Some components styles still need to be migrated from GitLab into GitLab UI, keep track of the progress in the epic: &1590.
The current SCSS architecture was designed to allow us both to gain the advantages of a utility CSS approach while also applying the same styles to both Vue components here in GitLab UI and HAML components in GitLab itself.
With utility mixins, styles are applied as a combination of single-attribute mixins:
@mixin gl-display-flex {
display: flex;
}
@mixin gl-text-red-800 {
color: $red-800;
}
.gl-my-component {
@include gl-display-flex;
@include gl-text-red-800;
}
..
<my-component class='gl-my-component' />
The advantages of this approach are:
-
It clarifies which colors, sizes, and other options are available within the design system. Rather than pulling values from specs or guessing, engineers are able to use already-vetted values. This also means that adding a new value becomes more deliberate and easier to check in reviews.
-
It makes it easier to cascade design-system changes, especially around text and spacing. That is because the utility mixins lend themselves to being updatable, like variables, in just one place. Consider the case of spacing: the values are taken from a scale (
gl-spacing-0
,gl-spacing-10
), which means updating from a base of4px
to6px
means updating just those mixins but keeping the relations the same.
We've decided to build both component classes and utility classes from the same mixins in order to
get these benefits while also having component CSS that can be applied in GitLab UI and GitLab
itself, Vue
and HAML
, without undue or repeated effort.
For even more detail on our decision making, RFCs #2
and #4 contain historical discussion around
these issues. After RFC #4 was approved,
we realized the silent class plus @extend
approach generated large amounts of CSS and the approach
was modified to use mixins and @include
instead. For more context,
see also this relevant discussion.
For more information about utility-first CSS, consider a post from Mike Crittenden, Ollie Williams on CSS Tricks or Sarah Dayan's Frontstuff.
Finally, to join in on discussion about HAML components, check out the following ongoing conversations:
- Add a variable (in
variables.scss
) if you are setting a base value in the design system — this is rare. - Add a utility mixin when you need it to style a component yet not available in the library. You should always base new utility mixins on the design system guidelines.
- Add a component class when implementing a new component’s styles.
GitLab UI provides a utility-class library based on the Pajamas Design System specifications for spacing, color, typography, and layers. This section provides guidelines to add new utilities.
The utility-class library is located in src/scss/utilities.scss
. GitLab UI generates the utility-class
library based on the mixins defined in src/scss/utility-mixins/
. To regenerate the file, run
the yarn generate-utilities
command.
Please note that while we generate the classes from the mixins, the mixins should be added manually,
as they are needed, and not generated using an @each
across values. This is for a few reasons:
-
To keep the bundle small. As mentioned above, we use component CSS defined with mixins so that HAML components can consume the same CSS without needing utility classes added in two places. However, this makes the bundle larger, because each new component adds new CSS. We try to offset this by not generating every possible utility.
-
To keep it easier to find classes. It is easier to grep for class names and see the values assigned to them when they are not generated in loops. This is good.
-
To understand the CSS we are actually using. By only including classes we use, we can review what we use at a glance.
We are aiming to build a set of styles that are easily reusable and component-agnostic. To help accomplish this:
- Place new utility mixins in one of the files located in the
src/scss/utility-mixins/
directory. Each file contains a group of related utilities likeborder
,color
,text
, and so on. - Give new mixins a generic name that describes what they do rather than what component they target.
For example:
// Bad
@mixin gl-alert-borders {
border-style: none;
}
// Good
@mixin gl-border-none {
border-style: none;
}
// Bad
@mixin gl-avatar-transitions {
transition-property: border-color;
}
// Good
@mixin gl-transition-property-border-color {
transition-property: border-color;
}
Time constraints or lack of stable design specifications might require you to create
component-specific mixins. In such cases, mixins should be defined in the component's
stylesheet and their names should be prefixed with gl-tmp-
instead of gl-
to make them easily
identifiable when paying technical debt in later development stages.
You can tell GitLab UI to generate stateful utilities based on a utility mixin. Stateful utilities
apply pseudo-class styles that target states like hover
, active
, focus
, and visited
. To
indicate that a utility mixin should have a stateful counterpart, add a default parameter to the
utility mixin declaration:
@mixin gl-border-none($hover: true) {
border-style: none;
}
After running yarn generate-utilities
, src/scss/utilities.scss
will contain the following
utility-classes:
.gl-border-none {
@include gl-border-none;
}
.gl-hover-border-none:hover {
@include gl-border-none;
}
You can define utility mixins that target specific breakpoints. Remember to use the breakpoint
variables defined in src/scss/variables
: $breakpoint-sm
, $breakpoint-md
, $breakpoint-lg
,
and $breakpoint-xl
. Here are some examples:
@mixin gl-md-border-none {
@include gl-media-breakpoint-up(md) {
border-style: none;
}
}
@mixin gl-xl-bg-white {
@include gl-media-breakpoint-up(xl) {
background-color: $white;
}
}
SCSS will generate a utility class enclosed in the breakpoint media query:
@media (min-width: 768px) {
.gl-md-border-none {
border-style: none;
}
}
@media (min-width: 1200px) {
.gl-xl-bg-white {
background-color: #fff;
}
}
For every utility mixin, we will automatically create an !important
version. These versions are
suffixed with !
to distinguish them from their normal counterparts. For example:
@mixin gl-border-none {
border-style: none;
}
// Generated automatically
@mixin gl-border-none\\! {
border-style: none !important;
}
There is however, one instance when this won't take place. This occurs when you are using an include
from another CSS file (say using a mixin from mixins.scss
in utilities.scss
). Although we create
the important variant, this won't have !important
suffixed to the rule. This is because the
post-processor is only aware of the file it is processing at that current time. For example:
.gl-str-truncated {
@include str-truncated
}
// Converts to (note the lack of !important)
.gl-str-truncated {
@include str-truncated
}
.gl-str-truncated\\! {
@include str-truncated
}
To make this work, you will need to manually enter the contents of the include into the file:
.gl-str-truncated {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: top;
white-space: nowrap;
max-width: 82%;
}
// Converts to
.gl-str-truncated {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: top;
white-space: nowrap;
max-width: 82%;
}
.gl-str-truncated\\! {
display: inline-block !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
vertical-align: top !important;
white-space: nowrap !important;
max-width: 82% !important;
}
More answers and details can be found in the SCSS style guide.