Core styles, components, macros and fields for BetterUp's website.
As BetterUp's website grows, the best way to scale our branding is to centralize styles and components. This NPM package is that centralization. Whenever a new project is spun up, this package should be included, which allows field presets, macros, styles, and scripts to be easily ported over.
If changes are needed to the global BetterUp branding, they can be made here, and propagated out to all projects.
-
/fields
- JS files for field partials. Each preset partial is designed to work fluidly with a macro.
- presets are all used with
hubspot-fields-js
package - some presets may accept a variable, depending on their usage.
-
/js
- JS scripts that may be used repeatedly. See each file for usage notes (coming soon)
-
/scss
- All core styles.
-
/base
- main variable values. This is the lowest level of SCSS breakdown, and should not be modified without review.
- None of the variables used here should be accessed directly in other scss files. Use
theme.scss
variables and utility classes instead.
-
/partials
- utility classes used in macros. All partials are imported into
utility-classes.scss
- utility classes used in macros. All partials are imported into
-
components.scss
- base mixins. Mixins are used in partials, but may also be used directly in module.scss files
-
theme.scss
- base theme variables. Use these variables to build components.
-
utility-classes.scss
- combined file of all partials. Import this into a new global scss file for the project
- This allows the code to only be loaded once for performance.
- combined file of all partials. Import this into a new global scss file for the project
-
macros.html
- Macros that use the field partials. See usage guidelines for more.
To install this package, run:
npm i bu-styleguide-v2
To install this package and save it to your project, run:
npm i bu-styleguide-v2 --save-dev
After downloading the package, you can include fields, js, styles, and macros as needed.
In your fields.js file, you can import a field partial at the top of your file with:
let background = require('bu-styleguide-v2/src/fields/background');
The imported field partial can then be used in your module.exports
as a function, i.e. background()
.
For more on field partials, see the hubspot-fields-js documentation here.
To import js from bu-styleguide-v2, import the file in a module.js or a js file. For example:
import 'bu-styleguide-v2/src/js/countup.js';
Would import countup.js
into your file, and be compiled on build.
Important: Include utility classes and font declarations at the top of your project stylesheet with:
@import 'bu-styleguide-v2/src/scss/utility-classes.scss';
@import "bu-styleguide-v2/src/scss/base/icons.scss";
@include loadIvarFonts;
@include loadSohneFonts;
It's recommended you include icons.scss
as well (our font awesome icons).
If you have a need for a single style partial to be included in a module.css or other css file, import it with:
@import 'bu-styleguide-v2/src/scss/utility-classes.scss'
In most BetterUp projects, macros.html
is copied when webpack starts from the npm package to your dist
folder, and then uploaded to HubSpot.
If you're developing improvements to bu-styleguide-v2 locally, you can point your project to the local version of the package using npm link
.
In /bu-styleguide-v2
, run:
npm link
In your project, run:
npm link {{local path to bu-styleguide-v2}}
If bu-styleguide-v2 is on my desktop, and my project is also on my desktop, I would run:
npm link ../bu-styleguide-v2
From the root of my project.
When your save a file in bu-styleguide-v2, it will then automatically be updated in your project.
Important: The hubspot-fields-js
CLI (command npm run generate-fields in most projects) must be exited and restarted after making changes to a field partial.
To update the npm package:
-
Confirm you're on the
main
branch and up-to-date. All code changes should be committed and pushed. Note that changes to the NPM package should be reviewed by Dillon. -
Run
npm version {{newVersion}}
. This will tag the package with a new version number. Use semantic versioning and replace {{newVersion}} with the appropriate update.- For example, if I'm on version 1.0.0 and just added a patch, I'll run
npm version 1.0.1
. - For more on semantic versioning, see here.
- For example, if I'm on version 1.0.0 and just added a patch, I'll run
-
Run
npm publish
. This will publish the new npm package. -
Run
git push
to push your changes to github.
Field partials, macros, and styles are designed to work fluidly together. See /examples
for mock modules that pull together a macro and it's associated field preset. For use of many macros and field presets at once, see the module library (/betterup theme).
- macro:
generateModal(options)
- fields:
modal.js
- styles:
modal.scss
. Included inutility-classes.scss
- output: a button that triggers a form, video, or video with copy ('mixed') modal.
- input: object. see below
generateModal()
and the modal.js
field preset both intake values. Note that this macro and fields are not currently used directly on production - instead, the modal macro and field preset are used by the CTA, video CTA, and link presets to keep modal options consistent.
For fields:
- First parameter: index. Should be a string. This allows you to input multiple modal options in a module without creating conflicting visibility settings (multiple fields with the same id).
- Second parameter: visibility field name, usually a choice field. When this field is equal to
modal
, the rest of the modal options will appear.
For the macro:
- Easiest to just pass in the field group.
- if not passing in the field group, can pass in an
options
object like below.
Example options, annotated:
{% set options = {
"index": 1,
"form": false, // set to true if a form modal
"video_embed": false, // set to true if a video modal
"vwo_goal_id": false, // set to a VWO goal id if necessary. Consult with CRO if needed
"headline": false, // pass in to add a headline to a "mixed" modal, with copy, a CTA, and a video
"description": false, // pass in to create a "mixed" modal, with copy, a CTA, and a video. This value will be checked to determine which kind of modal
"button_link": false, //pass in to add a CTA to your mixed modal. Destination of the CTA. Should be an object based on a link field.
"button_label": false, // pass in to add a CTA to your mixed modal. Destination of the button label
"tracking_class": false // pass in to add a tracking class to the mixed modal CTA
}
%}
Because this isn't used directly, there's no example provided for this snippet. Instead, refer to examples/cta-module.module
for usage.
- macro:
generateStyleWarning(string)
- fields: none
- styles: Not used for production. Included in
utility-classes.scss
. - output: Block of HTML that is only visible in the page editor.
- input: no fields
This macro is used to create a "style warning" within a module. This warning will only appear in the editor, and is only designed to work with a module library module.
The macro accepts one parameter - the text to be displayed as a warning. There is then a link to the module library documentation.
Because this doesn't have any fields, it's only used in an html file, such as:
{% if fieldA == 'value' && fieldB == 'value' %}
{{generateStyleWarning('FieldA and FieldB cannot have the same value.)}}
{% endif %}
For a full example, see the generateCTA
macro.
- macro:
generateCTA(group)
- fields:
cta.js
orctaVisibility.js
orctaArray.js
- styles:
cta.scss
. Included inutility-classes.scss
- output: 1 or 2 CTAs that can link to another page or a modal.
- input: varies based on fields. For most basic option,
module.set_ctas
.
generateCTA
intakes the entire field preset generated by cta.js. It should be added wherever a CTA is needed in the DOM.
There are several field presets for a CTA based on a variety of uses.
-
cta.js
- is the most basic option. The field preset does not take in any options. Limited to 1 per module because of visibility. -
ctaVisibility.js
- more complex option. Takes in an object with an index, dependency field name, and value for that dependency. Used with the same CTA macro. However, this field preset can be used to generate CTA options that aren't always visible, and more than one per module.
Example object:
let CTAOptions = {
dependency: 'content_type', // name of field to base visibility on. Usually a choice field
value: 'text|stats|bullets', // field value to base visibility on
index: '1' // index, to allow more than 1 instance of CTAs per module
}
-
ctaArray.js
- deconstructed CTA option. Used with the same CTA macro. This is an array of the same field preset, which allows the CTA field preset to be nested further within a module group. I didn't realize this was possible the first time I built this - it might be better for the user experience.- An object of CTA options must be passed into the macro for this to work.
Example object:
{% set ctaOptions = {
include_cta_1: module.include_cta_1,
cta_1: module.cta_1,
include_cta_2: module.include_cta_2,
cta_2: module.cta_2,
}
%}
Outputs 1 or 2 CTAs based on options. Note than an editor error is thrown if a primary CTA & secondary CTA are used next to eachother.
For an example of all three field groups, see examples/section-width-module.module
.
- macro:
generateBackground(group)
- fields:
background.js
,backgroundSimple.js
- styles:
background.scss
. Included inutility-classes.scss
- output: classes & parameters to render a background image
- input:
module.background___{index}
(default) & class array variable for macro, {index} for field
generateBackground
intakes the field group generated by background.js
or backgroundSimple.js
. It should be added to the outermost wrapper of a module, so that a user can adjust the overall width of the section.
background.js
is the full set of background options, including an image, bespoke mobile image, and lazy loading. backgroundSimple.js
is a simplified version of this set, only supporting our core background colors (black, white, and gray). This should be used in modules where the foreground could get difficult to read on a background image, such as a form.
Whem adding the background.js
field preset, an index should be passed into the function so that multiple backgrounds can be used per module. This index can be a string or a number, and is then accessed with module.background___{index}
.
backgroundSimple.js
does not take an index.
example:
let background = require('bu-styleguide-v2/src/fields/background');
module.exports = [
background('1'),
]
vs.
let backgroundSimple = require('bu-styleguide-v2/src/fields/backgroundSimple');
module.exports = [
backgroundSimple(),
]
This macro outputs a string of classes and several parameters for the wrapper element. It must be used with a class variable, and this class variable should be an array. The class array should be passed into the macro, and then used in the element's class paramater with HubL's |join
.
Example:
{% set wrapperClasses = [] %}
{% set background = generateBackground(module.background__main, wrapperClasses) %}
<section {{background}} class="{{wrapperClasses|join(' ')}}">
</section>
For an example, see examples/background.module
.
- macro:
generateSectionWidthClass(field)
- fields:
sectionWidth.js
- styles:
sectionWidth.scss
. Included inutility-classes.scss
- output: string of classes
- input:
module.section_width
(default) for macro, array of choices for field
generateSectionWidthClass
intakes the field generated by sectionWidth.js
. It should be added to the outermost wrapper of a module, so that a user can adjust the overall width of the section.
When adding the sectionWidth field preset, you'll need to specify which width options are available. This should be an array of arrays, so that it can be used in a choice field. If an unsupported width option is added, the new option will need to be custom styled from the module.
Supported width options:
let widthOptions = [
['full-bleed', 'Full bleed'],
['full-width', 'Full width'],
['narrow', 'Narrow']
]
This macro outputs a string of classes. It can be used with a class variable and .append
to make your module more readable, or directly in the class list of an element.
For an example, see examples/section-width-module.module
.
- macro:
generateContentWidthClass(field)
- fields:
contentWidth.js
- styles:
contentWidth.scss
. Included inutility-classes.scss
- output: string of classes
- input:
module.content_width
(default)
generateContentWidthClass
intakes the field generated by contentWidth.js
. It should be added to the content wrapper of a module, so that a user can adjust the overall content width. Note that the content wrapper is not the outermost element of the module, but usually the direct child of that outermost element.
This macro outputs a string of classes. It can be used with a class variable and .append
to make your module more readable, or directly in the class list of an element.
For an example, see examples/content-width-module.module
.
- macro:
generateSpacingClasses(group)
- fields:
spacing.js
- styles:
spacing.scss
. Included inutility-classes.scss
- output: string of classes
- input:
module.spacing
(default)
generateSpacingClasses
intakes the entire field group generated by spacing.js
. It should be added to the outermost wrapper of a module, so that a user can adjust margins and padding within a flexible column.
This macro outputs a string of classes. It can be used with a class variable and .append
to make your module more readable, or directly in the class list of an element.
For an example, see examples/spacing-module.module
.
- macro:
generateHeading(group)
- fields:
heading.js
- styles:
heading.scss
. Included inutility-classes.scss
- output: an heading tag (H1 through H6) styled based on style selection (H1 through H7)
- input:
module.heading
(default)
generateHeading
intakes the entire field group generated by heading.js
. This allows a user to select the correct semantic Heading tag (for SEO & accessibility) but also style it as needed.
The default field group name is module.heading
. If this field preset is nested within a group, pass in module.groupName.heading
.
Note that BetterUp has an additional heading style - H7 - that is a smaller version of the H1.
If no heading label is input, nothing will appear.
For an example, see example.heading-module.module
.
- macro:
generateVideoCTA(group)
- fields:
videoCTA.js
- styles:
videoCTA.scss
. Included inutility-classes.scss
- output: an heading tag (H1 through H6) styled based on style selection (H1 through H7)
- input:
module.video_cta
(default) for macro, dependent field name for videoCta.js
generateVideoCTA(group)
intakes the entire field group created by videoCTA.js
. It will render a light or dark video CTA that links to a modal.
The preset field group from videoCTA.js
intakes the name of a boolean field that this field group's visibility will be dependent on.
Example:
const { Group, Field } = require("@igomoon/hubspot-fields-js");
let videoCTA = require('bu-styleguide-v2/src/fields/videoCTA');
module.exports = [
Field.boolean()
.name('add_video_cta', 'Add a video CTA?')
.id('add_video_cta')
.visibleIf('content_type', 'video', 'EQUAL')
.inlineHelpText('Check this box to add a video play button to your section.'),
videoCTA('add_video_cta')
]
For an example, see examples/video-cta-module.module
.
- macro:
generateLink(object)
- fields:
linkSimple.js
- styles: N/A, functionality only
- output: a link or a button that can go to another page or open a modal.
- input: object for macro, index for field preset
generateLink
creates an element wrapped in an a
element linking to another page or wrapped in a button
element linking to a modal. It intakes an object, and is commonly used with a repeating field group. An example use case for generateLink is creating a linked card.
Because generateLink
can intake an element, it's recommended the inner HTML of that element is abstracted to a macro.
Example:
// extracted macro
{% macro innerContent(field) %}
{{field}}
{% endmacro %}
<div>
{% set elementWrapperClasses = [] %}
{% set innerContent = innerContent(module.field) %}
{% set params = {
'group': module.link__1 // linkSimple.js field group
'classList': elementWrapperClasses,
'innerContent': innerContent,
'index': loop.index,
'additionalParams': false // can be background, or any other param needed in the button or a element
} %}
{{ macros.generateLink(params)}}
{% else %}
</div>
The generateLink
macro uses the generateModal
macro to generate modals.
When adding the linkSimple.js
field preset, an index should be passed into the function so that multiple linked items can be used per module. This index can be a string or a number, and is then accessed with module.link__{index}
.
For an example, see examples/simple-link-module.module
.
- macro:
generateAnchor()
- fields:
anchor
- styles: N/A, functionality only
- output: id, which can then be linked to elsewhere on the page
- input: none
The anchor module does not require any options, as it uses the module context. It should be added the the outermost wrapper of a module, so that it can be appropriately linked with an anchor.
This macro outputs an id.
For an example, see examples/anchor-module.module
.
- macro:
generateBackgroundVideo(group)
- fields:
backgroundVideo.js
- styles:
backgroundVideo.scss
. Included inutility-classes.scss
- output: classes & parameters to render a background video
- input:
module.background-video___{index}
(default) & class array variable for macro, {index} for field
generateBackgroundVideo
intakes the field group generated by backgroundVideo.js
. It should be added to the bottom of the container and the styles will force a full screen video with poster.
backgroundVideo.js
includes a poster image, video url field, lazy loading and a mobile poster image. The background group should be set to Black or White when the backgroundVideo is populated to ensure we are not loading more images on page than we need to be.
Example:
const { Group, Field } = require("@igomoon/hubspot-fields-js");
let backgroundVideo = require('bu-styleguide-v2/src/fields/backgroundVideo');
module.exports = [
backgroundVideo('1')
]
Not in use.
Not in use.