Sass: An extension to CSS that reduces the repetition in CSS and allows developers to use shared functions, mixins and variables. [^1]
Members: Refers to variables, functions and mixins in Sass.
Sass provides members that make it easier to reuse code throughout the Ionic Framework repository. Variables hold values that can be used by other stylesheets. Mixins define reusable blocks of styles that can be included in other selectors. Functions allow the manipulation of values and can perform calculations.
The purpose of this document is to identify the scenarios in which Sass variables should be used.
In Ionic Framework v1 through v3, the project was built with Sass variables that developers could change at runtime. While the default values were provided by Ionic Framework, anyone developing with it could override these values to rebuild the Ionic Framework CSS with their own values. [^2]
Due to this, Ionic Framework documented the Sass variables as part of the public API using @prop
comments as early as v2.0.0:
// alert.ios.scss
/// @prop - Max width of the alert
$alert-ios-max-width: 270px !default;
/// @prop - Border radius of the alert
$alert-ios-border-radius: 13px !default;
If a Sass variable was deprecated or hidden from the public API, the @prop
comment would be removed, or it would never be added, as seen in v3.9.2:
// alert.ios.scss
// deprecated
$alert-ios-head-padding: null !default;
To ensure proper documentation of variables for customizing Ionic Framework, Sass variables were added for components even if they were not used multiple times within the same component or elsewhere:
// alert.ios.scss
/// @prop - Text color of the label for the checked radio alert
$alert-ios-radio-label-text-color-checked: $alert-ios-button-text-color !default;
.alert-ios [aria-checked=true] .alert-radio-label {
color: $alert-ios-radio-label-text-color-checked;
}
The abundance of Sass variables currently in Ionic Framework is a result of their historical usage, being used to rebuild the CSS and customize Ionic Framework components.
The comments for Sass variables are also still visible today in v7.7.0, even though they are no longer used by any documentation generators:
// alert.ios.vars.scss
/// @prop - Max width of the alert
$alert-ios-max-width: dynamic-font-clamp(1, 270px, 1.2) !default;
/// @prop - Border radius of the alert
$alert-ios-border-radius: 13px !default;
These comments aren’t necessary when the naming describes its use thoroughly. The comments for the variables above do not need to be there, as it is fairly obvious what they are used for.
However, the comment for the following variable might be helpful in explaining where it is used because on first glance it reads like it could be used for a sub title inside of a title:
// action-sheet.ios.vars.scss
/// @prop - Font weight of the action sheet title when it has a sub title
$action-sheet-ios-title-with-sub-title-font-weight: 600 !default;
It could be argued though that the comment doesn’t really help, as seeing the variable in use will explain its purpose the best. Additionally, this is an example of a variable that isn’t necessary, given it is only used in one place, which is why it is so specific in the first place.
There are two things that need to be outlined here: when we should use comments and when we should use variables. The sections below detail the recommended usage for each of these.
We should update the comments for Sass variables in one of the following ways:
// alert.ios.vars.scss
-/// @prop - Border radius of the alert
+// Border radius of the alert
$alert-ios-border-radius: 13px !default;
// alert.ios.vars.scss
-/// @prop - Border radius of the alert
$alert-ios-border-radius: 13px !default;
The table below outlines the recommended approach for when to use Sass variables. Each scenario links to a section that explains it in more detail.
Scenario | |
---|---|
✅ | Global |
✅ | Theming |
✅ | Reusable values |
✅ | Media queries |
✅ | Dynamic calculations |
🚫 | Consistency |
🚫 | Text Alignment |
🚫 | Structural Changes |
🚫 | Font Properties |
Global variables that are used in multiple places include font-family
, z-index
, and opacity
. These should continue to be set in variables as they affect multiple components that use them.
Example of global variables:
// ionic.globals.scss
$font-family-base: var(--ion-font-family, inherit) !default;
$hairlines-width: .55px !default;
$placeholder-opacity: 0.6 !default;
Storing colors and other design-related values makes it easy to update an entire theme by modifying a few variables.
Example of theme variables:
// ionic.theme.default.scss
$background-color-value: #fff !default;
$background-color-rgb-value: 255, 255, 255 !default;
$text-color-value: #000 !default;
$text-color-rgb-value: 0, 0, 0 !default;
$background-color: var(--ion-background-color, $background-color-value) !default;
$background-color-rgb: var(--ion-background-color-rgb, $background-color-rgb-value) !default;
$text-color: var(--ion-text-color, $text-color-value) !default;
$text-color-rgb: var(--ion-text-color-rgb, $text-color-rgb-value) !default;
// ionic.theme.default.ios.scss
$backdrop-ios-color: var(--ion-backdrop-color, #000) !default;
$overlay-ios-background-color: var(--ion-overlay-background-color, var(--ion-color-step-100, #f9f9f9)) !default;
Use variables for values that are repeated throughout stylesheets, such as spacing, border-radius
, font-size
, or any other value used in multiple places. A value should only be considered reusable if it is used more than once and related among the elements it is being applied to in some way. For instance, a value is not considered related if it changes a common property, such as border style. While many components use border-style: solid
, it does not need to be stored unless these components will require updates with design changes. Currently, the border styles have consistently been set to solid
, with the exception of none
for a CSS reset.
Example of reusable values:
Do ✅ | |
---|---|
```scss // alert.ios.vars.scss /// @prop - Padding end of the alert head $alert-ios-head-padding-end: 16px !default; /// @prop - Padding start of the alert head $alert-ios-head-padding-start: $alert-ios-head-padding-end !default; ``` ```scss // alert.ios.scss .alert-head { padding-top: 12px; padding-inline-end: $alert-ios-head-padding-end; padding-bottom: 7px; padding-inline-start: $alert-ios-head-padding-start; } ``` | |
Don't :x: | |
```scss // alert.ios.vars.scss /// @prop - Padding top of the alert head $alert-ios-head-padding-top: 12px !default; /// @prop - Padding bottom of the alert head $alert-ios-head-padding-bottom: 7px !default; ``` ```scss // alert.ios.scss .alert-head { padding-top: $alert-ios-head-padding-top; padding-bottom: $alert-ios-head-padding-bottom; } ``` |
Do ✅ | |
---|---|
```scss // ionic.theme.default.md.scss $global-md-item-padding-end: 16px; $global-md-item-padding-start: $global-md-item-padding-end; ``` ```scss // item.md.vars.scss @import "../../themes/ionic.globals.md"; /// @prop - Padding end for the item content $item-md-padding-end: $global-md-item-padding-end !default; /// @prop - Padding start for the item content $item-md-padding-start: $global-md-item-padding-start !default; ``` ```scss // item-divider.md.vars.scss @import "../../themes/ionic.globals.md"; /// @prop - Padding start for the divider $item-divider-md-padding-start: $global-md-item-padding-start !default; /// @prop - Padding end for the divider $item-divider-md-padding-end: $global-md-item-padding-end !default; ``` | |
Don't :x: | |
```scss // item.md.vars.scss @import "../../themes/ionic.globals.md"; /// @prop - Padding end for the item content $item-md-padding-end: 16px !default; /// @prop - Padding start for the item content $item-md-padding-start: 16px !default; ``` ```scss // item-divider.md.vars.scss @import "../../themes/ionic.globals.md"; @import "../item/item.md.vars"; /// @prop - Padding start for the divider $item-divider-md-padding-start: $item-md-padding-start !default; /// @prop - Padding end for the divider $item-divider-md-padding-end: $item-md-padding-end !default; ``` |
Do ✅ | |
---|---|
```scss // chip.vars.scss /// @prop - Unitless font size of the chip before scaling $chip-base-font-size: 14; /// @prop - Font size of the chip in rem before scaling $chip-base-font-size-rem: #{math.div($chip-base-font-size, 16)}rem; /// @prop - Size of an icon within a chip (in em to scale as the font size of the chip scales) $chip-icon-size: math.div(20em, $chip-base-font-size); /// @prop - Size of an avatar within a chip (in em to scale as the font size of the chip scales) $chip-avatar-size: math.div(24em, $chip-base-font-size); ``` | |
Don't :x: | |
```scss // alert.vars.scss /// @prop - Font size of the alert button $alert-button-font-size: dynamic-font(14px) !default; ``` |
Do ✅ | |
---|---|
```scss // label.md.vars.scss /// @prop - Text color of the stacked/floating label when it is focused $label-md-text-color-focused: ion-color(primary, base) !default; ``` ```scss // label.md.scss :host-context(.ion-focused).label-stacked:not(.ion-color), :host-context(.ion-focused).label-floating:not(.ion-color), :host-context(.item-has-focus).label-stacked:not(.ion-color), :host-context(.item-has-focus).label-floating:not(.ion-color) { color: $label-md-text-color-focused; } ``` | |
Don't :x: | |
```scss // label.ios.vars.scss /// @prop - Text color of the stacked/floating label when it is focused $label-ios-text-color-focused: null !default; ``` ```scss // label.ios.scss :host-context(.ion-focused).label-stacked:not(.ion-color), :host-context(.ion-focused).label-floating:not(.ion-color), :host-context(.item-has-focus).label-stacked:not(.ion-color), :host-context(.item-has-focus).label-floating:not(.ion-color) { color: $label-ios-text-color-focused; } ``` |
Do ✅ | Don't :x: |
---|---|
```scss // action-sheet.ios.scss :host { text-align: center; } .action-sheet-title { text-align: center; } ``` | ```scss // action-sheet.ios.vars.scss /// @prop - Text align of the action sheet $action-sheet-ios-text-align: center !default; ``` ```scss // action-sheet.ios.scss :host { text-align: $action-sheet-ios-text-align; } .action-sheet-title { text-align: $action-sheet-ios-text-align; } ``` |
Do ✅ |
---|
```scss // alert.ios.scss .alert-button-group { flex-wrap: wrap; } .alert-button { flex: 1 1 auto; } ``` |
Don't :x: |
```scss // alert.ios.vars.scss /// @prop - Flex wrap of the alert button group $alert-ios-button-group-flex-wrap: wrap !default; /// @prop - Flex of the alert button $alert-ios-button-flex: 1 1 auto !default; ``` ```scss // alert.ios.scss .alert-button-group { flex-wrap: $alert-ios-button-group-flex-wrap; } .alert-button { flex: $alert-ios-button-flex; } ``` |
Do ✅ |
---|
```scss // action-sheet.ios.scss .action-sheet-title { font-size: dynamic-font-min(1, 13px); font-weight: 400; } .action-sheet-sub-title { font-size: dynamic-font-min(1, 13px); font-weight: 400; } ``` |
Don't :x: |
```scss // action-sheet.ios.vars.scss /// @prop - Font size of the action sheet title $action-sheet-ios-title-font-size: dynamic-font-min(1, 13px) !default; /// @prop - Font weight of the action sheet title $action-sheet-ios-title-font-weight: 400 !default; /// @prop - Font size of the action sheet sub title $action-sheet-ios-sub-title-font-size: dynamic-font-min(1, 13px) !default; ``` ```scss // action-sheet.ios.scss .action-sheet-title { font-size: $action-sheet-ios-title-font-size; font-weight: $action-sheet-ios-title-font-weight; } .action-sheet-sub-title { font-size: $action-sheet-ios-sub-title-font-size; font-weight: $action-sheet-ios-title-font-weight; } ``` |