Frontend Technical Approach
Lea Bialachowski and Matthew Neil

Day one
Planning and not coding!
Repeated UI patterns extracted into core modules, approach to problems, layout used, spacing top or bottom for panel elements
UI Pattern Identification
List and link list, tabs, gradients
Example UI patterns


Folder architecture

Task Runner
Quicker than web essentials, easy to use, more configurable, does more things that are useful (image compression, concat without bundling), doesn't sudenly break or crash

/// <vs SolutionOpened='images' />
module.exports = function (grunt) {
// load nom tasks from package.json
require('load-grunt-tasks')(grunt);
// show timing of build tasks
require('time-grunt')(grunt);
// just in time load tasks / plugins faster
require('jit-grunt')(grunt)({
customTasksDir: 'grunt_tasks'
});
// global folder configuration
var options = {
config: {
src: "grunt_tasks/*.js"
},
paths: {
localDir: 'C:/inetpub/wwwroot/BurgesSalmon/Website',
localHost: 'BurgesSalmon/',
// css paths
cssSrc: 'src/css',
cssDist: 'dist/css',
// scripts paths
jsSrc: 'src/scripts',
jsDist: 'dist/scripts',
jsTest: 'src/scripts/specs',
// image paths
imgSrc: 'src/images',
imgDist: 'dist/images',
// Sitecore modules
wffmSrc: 'src/css/modules/wffm',
wffmDist: 'sitecore modules/Shell/Web Forms for Marketers/Themes',
wffmCssFileName: 'burges-salmon'
}
};
// Load grunt configurations automatically
var configs = require('load-grunt-configs')(grunt, options);
// Define the configuration for all the tasks
grunt.initConfig(configs);
// Default task(s).
grunt.registerTask('default', ['watch']);
grunt.registerTask('styles', ['less:main', 'postcss:dist', 'cssmin:main', 'newer:imagemin', 'copy:styles']);
grunt.registerTask('scripts', ['newer:jshint:all', 'concat', 'uglify', 'copy:scripts']);
grunt.registerTask('images', ['newer:imagemin', 'newer:svgmin', 'copy:images']);
grunt.registerTask('test', ['jasmine']);
grunt.registerTask('wffm', ['less:wffm', 'postcss:wffm', 'cssmin:wffm', 'copy:wffm']);
// js debug
grunt.registerTask('jsdebug', ['concat', 'uglify', 'copy:scripts']);
grunt.registerTask('prod', [
'jshint:all',
'concat',
'uglify',
'less:main',
'postcss:dist',
'cssmin:main',
'less:wffm',
'postcss:wffm',
'cssmin:wffm',
'copy:wffm',
'imagemin',
'copy:prod'
]);
};
Writing the code
- extending WFFM styling
- avoiding mixins
- using variables for everything
- using less functions such as darken when possible rather than creating new variables for a new HEX value

Code snippet
@import (reference) "../../core/_breakpoints.less";
@import (reference) "../../core/_variables.less";
.profile-card {
box-sizing: border-box;
color: @dark-grey;
margin-top: @division-medium;
width: 100%;
display: flex;
background-size: 100% 105px;
position: relative;
top: 0;
transition: top 0.25s @cubic-1, box-shadow 0.3s @cubic-1;
box-shadow: 0px 3px 10px rgba( 0, 0, 0, 0);
&__content-container {
width: 100%;
background: linear-gradient( @grey-100, @grey-100) no-repeat 0 105px;
position: relative;
align-self: stretch;
display: flex;
&:before {
content: '';
transition: all 1s;
opacity: 1;
background: @grey-150 linear-gradient( @grey-100, @grey-100) no-repeat 0 105px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 4;
}
&:hover {
&:before {
opacity: 0;
}
.profile-card__name:after {
background: @bs-magenta;
}
}
}
&__content {
position: relative;
z-index: 5;
padding: 30px 15px 90px 15px;
align-self: stretch;
width: 100%;
}
&__name {
margin-bottom: 15px;
}
&__title {
font-size: 75%;
display: block;
}
&__list-label {
padding-top: 10px;
display: block;
text-align: left;
padding-left: 17px;
font-size: 105%;
}
&__list {
text-align: left;
padding-top: 0;
@media @tablet {
li:nth-child(n+4){
display: none;
}
}
}
&__button-container {
position: absolute;
bottom: 20px;
left: 0;
right: 0;
}
}Variables, darken, lighten
Code snippets
@import (reference) "../../core/_breakpoints.less";
@import (reference) "../../core/_grid.less";
@import (reference) "../../core/_utilities.less";
// Developer notes:
// The wffm selectors have been extended to the _forms.less file to reduce duplicated styles
// Required wffm selectors are then output to the sitecore modules folder...
// sitecore modules\Shell\Web Forms for Marketers\Themes
.scfForm {
.page-editor &,
.page-preview & {
font-family: 'Frutiger Pro 45 Light';
}
.scfSectionBorderAsFieldSet {
&:extend(fieldset);
text-align: left;
}
.scfSectionLegend {
&:extend(legend);
.page-editor &,
.page-preview & {
font-family: inherit;
font-weight: inherit;
font-size: inherit;
color: inherit;
}
}
.scfTitleBorder {
.page-editor &,
.page-preview & {
// Used to overide the default styles injected via page editor
text-align: center;
font-family: inherit;
}
}
.scfDropListLabel,
.scfEmailLabel,
.scfMultipleLineTextLabel,
.scfSingleLineTextLabel,
.scfPasswordLabel,
.scfNumberLabel,
.scfDatePickerLabel,
.scfDateLabel,
.scfRadioButtonListLabel,
.scfListBoxLabel,
.scfFileUploadLabel,
.scfDateSelectorLabel,
.scfCreditCardLabel,
.scfConfirmPasswordLabel,
.scfCaptchaLabel,
.scfTelephoneLabel,
.scfSmsTelephoneLabel {
&:extend(label);
order: 0;
.page-editor &,
.page-preview & {
// Used to overide the default styles injected via page editor
&:extend(label);
order: 0;
width: auto;
}
}
.scfCheckBoxListLabel {
&:extend(label);
order: 0;
}
.ui-icon-datepicker {
display: none;
}
.scfCheckboxBorder {
display: flex;
flex-wrap: wrap;
> .scfCheckBoxListGeneralPanel {
width: auto;
}
.scfCheckboxUsefulInfo {
+ .scfRequired {
order: 4;
}
}
}
.scfCheckBoxListGeneralPanel {
order: 2;
width: 100%;
}
.scfRadioButtonList,
.scfCheckBoxList {
label {
margin: @division-small / 2 0 0;
}
}
.scfSingleLineTextBorder,
.scfDropListBorder,
.scfEmailBorder,
.scfMultipleLineTextBorder,
.scfSingleLineTextBorder,
.scfPasswordBorder,
.scfNumberBorder,
.scfDateBorder,
.scfRadioButtonListBorder,
.scfListBoxBorder,
.scfCheckBoxListBorder,
.scfFileUploadBorder,
.scfDateSelectorBorder,
.scfCreditCardBorder,
.scfConfirmPasswordBorder,
.scfCaptchaBorder,
.scfTelephoneBorder,
.scfSmsTelephoneBorder {
box-sizing:border-box;
display: flex;
flex-wrap: wrap;
width: 100%;
padding:0 @division-small/2;
}
.scfEmailGeneralPanel,
.scfMultipleLineGeneralPanel,
.scfSingleLineGeneralPanel,
.scfPasswordGeneralPanel,
.scfNumberGeneralPanel,
.scfDateGeneralPanel,
.scfRadioButtonListGeneralPanel,
.scfFileUploadGeneralPanel,
.scfDateSelectorGeneralPanel,
.scfCreditCardGeneralPanel,
.scfConfirmPasswordGeneralPanel,
.scfCaptchaGeneralPanel,
.scfTelephoneGeneralPanel,
.scfSmsTelephoneGeneralPanel,
.scfDropListGeneralPanel,
.scfListBoxGeneralPanel,
.scfDatePickerGeneralPanel {
text-align: left;
order: 3;
width: 100%;
}
.scfDateSelectorGeneralPanel {
label {
}
.scfDateSelectorShortLabelDay,
.scfDateSelectorShortLabelMonth,
.scfDateSelectorShortLabelYear,
.scfDateSelectorYear,
.scfDateSelectorMonth,
.scfDateSelectorDay {
display: inline-block;
width: calc(100% / 3);
border: 5px solid @white;
box-sizing: border-box;
.page-editor &,
.page-preview & {
color: inherit;
font-size: inherit;
float: none;
line-height: inherit;
padding: 0;
}
}
.scfDateSelectorYear,
.scfDateSelectorMonth,
.scfDateSelectorDay {
min-height: 60px;
}
}
.scfListBox,
.scfDropList {
}
select {
&:extend(select);
}
textarea {
&:extend(textarea);
}
input {
&[type="text"],
&[type="date"],
&[type="email"],
&[type="month"],
&[type="number"],
&[type="password"],
&[type="tel"],
&[type="time"],
&[type="week"],
&[type="url"] {
&:extend(input[type="text"]);
}
}
// form validation
.scfValidatorRequired,
.scfRequired {
color: @dark-grey;
order: 1;
margin: 25px 0 0 10px;
}
.scfValidator {
}
.scfIntroBorder {
clear: both;
.page-editor &,
.page-preview & {
text-align: center;
}
}
.scfFooterBorder {
text-align: center;
}
.scfError,
.scfValidationSummary,
.scfValidatorRequired,
.scfValidator {
&:extend(.input-error);
}
.scfValidationSummary {
border: 2px solid @bs-red;
background: fade(@bs-red, 10%);
margin-top: @division-small;
ul {
padding: @division-small;
li {
list-style: none;
&:before {
display: none;
}
}
}
}
.scfCreditCardTextUsefulInfo,
.scfConfirmPasswordUsefulInfo,
.scfDateSelectorUsefulInfo,
.scfCaptchaUsefulInfo,
.scfTelephoneUsefulInfo,
.scfSmsTelephoneUsefulInfo,
.scfPasswordUsefulInfo,
.scfSingleLineTextUsefulInfo,
.scfMultipleLineTextUsefulInfo,
.scfEmailUsefulInfo,
.scfNumberUsefulInfo,
.scfDatePickerUsefulInfo,
.scfDropListUsefulInfo,
.scfListBoxUsefulInfo,
.scfRadioButtonListUsefulInfo,
.scfCheckBoxListUsefulInfo,
.scfFileUploadUsefulInfo,
.scfCheckboxUsefulInfo {
display: block;
margin: 5px 0 0;
color: fade(@bs-black, 50%);
text-align: left;
order: 5;
width: 100%;
}
.scfSubmitButton:extend(.btn) {
// Duplicated style as you cannot extend nested &--selector
height: 60px;
line-height: 62px;
background: @bs-magenta url(/dist/images/icons/Icons_Arrow_White.svg) no-repeat center right 20px;
color: @white;
background-size: 10px;
&:hover:extend(.btn:hover) {
// this extends to the _forms.less file on compile
}
}
.scfSubmitButtonBorder {
.page-editor &,
.page-preview & {
text-align: center;
}
}
.scfSectionContent {
display: flex;
flex-wrap: wrap;
}
/* BASE SITECORE CLASSES */
/** Sitecore added classes applied to form elements **/
// form 50/50 split columns sitecore set in webforms
.halfAvailableWidth,
.thirdAvailableWidth {
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
float: none;
padding: 0 @division-small/2;
width: 100%;
}
.halfAvailableWidth {
@media @tablet {
width: 50%;
}
}
.thirdAvailableWidth {
@media @tablet {
width: calc(100%/3);
}
}
/* CUSTOM KAGOOL CLASSES */
.two-column,
.three-column {
tbody {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
tr {
box-sizing: border-box;
width: 100%;
}
}
.two-column {
tr {
@media @tablet {
width: 50%;
}
}
}
.three-column {
tr {
@media @tablet {
width: calc(100% / 3);
}
}
}
}Extending and avoiding mixins
@import (reference) "_variables.less";
@import (reference) "_breakpoints.less";
@import (reference) "_typography.less";
// Mixin used as range slider requires specific browser selectors :(
.range-thumb() {
background: @bs-magenta;
border-radius: 20px;
width: 20px;
height: 20px;
cursor: pointer;
}
/* BASE FORM */
fieldset {
border:none;
padding:0;
margin:@division-small 0 0;
}
legend {
.short-text();
text-align:center;
width:100%;
&:after {
&:extend(h2:after);
}
}
label {
display:block;
text-align:left;
font-size: @base-font-size * @p;
margin:30px 0 0;
line-height:1;
}
input {
&[type="text"],
&[type="email"],
&[type="password"],
&[type="date"],
&[type="month"],
&[type="number"],
&[type="datetime"],
&[type="datetime-local"],
&[type="tel"],
&[type="time"],
&[type="week"],
&[type="url"],
&[type="search"] {
box-sizing: border-box;
border-radius:0;
border: 2px solid @grey-100;
background: @grey-100;
color:@grey-80;
min-height: 50px;
margin: @division-small 0 0;
padding: 10px @division-small;
width: 100%;
text-align: left;
-webkit-appearance: none; /* iOS input madness */
&:focus {
-webkit-appearance: none; /* iOS input madness */
outline: none; /* remove chrome blue outline */
}
&:required {
border-bottom-color: @bs-red;
}
}
&[type="datetime"],
&[type="datetime-local"] {
padding: 10px 5px 10px @division-small;
}
&[type="file"] {
margin: @division-small 0 0;
}
// RANGE ALL BROWSERS
&[type="range"] {
width: 100%;
-webkit-appearance: none;
&:focus {
outline: none;
}
/* WEBKIT */
&::-webkit-slider-thumb {
-webkit-appearance: none;
margin-top: -5px;
.range-thumb();
}
&::-webkit-slider-runnable-track {
animation: 0.2s;
background: @grey-100;
cursor: pointer;
height: 8px;
width: 100%;
transition: background 0.25s @cubic-1;
}
/* FIREFOX */
&::-moz-range-thumb {
border: none;
margin-top: -5px;
.range-thumb();
}
&::-moz-range-track {
animation: 0.2s;
background: @grey-100;
cursor: pointer;
height: 8px;
width: 100%;
transition: background 0.25s @cubic-1;
}
/* IE */
&::-ms-track {
animate: 0.2s;
width: 100%;
height: 8px;
cursor: pointer;
background: transparent;
border-color: transparent;
border-width: 8px 0;
color: transparent;
}
&::-ms-fill-upper,
&::-ms-fill-lower,
&:focus::-ms-fill-upper,
&:focus::-ms-fill-lower {
background: @grey-100;
}
&::-ms-thumb {
border: none;
.range-thumb();
}
}
&[type="checkbox"] {
display: none;
+ label {
position: relative;
}
&:checked {
+ label:before {
content: '\2713';
color: @bs-green;
}
}
+ label:before {
box-sizing: border-box;
content: '';
cursor: pointer;
width: 20px;
height: 20px;
border: 1px solid @dark-grey;
display: inline-block;
vertical-align: middle;
margin-right: 10px;
text-align: center;
}
}
&[type="radio"] {
display: none;
+ label {
position: relative;
}
&:checked {
+ label:before {
background: @dark-grey;
}
}
+ label:before {
content: '';
width: 8px;
height: 8px;
display: inline-block;
vertical-align: middle;
margin: 0 16px 0 6px;
text-align: center;
border-radius: 50%;
background: @white;
box-shadow: 0 0 0 5px @white, 0 0 0 6px @dark-grey;
}
}
&[type="search"] {
background-image: url(/dist/images/icons/Icons_Search.svg);
background-repeat: no-repeat;
background-position: center right 10px;
background-size: 20px;
}
&[type="submit"],
&[type="reset"] {
margin-left: @gutter-width-px;
margin-right: @gutter-width-px;
}
}
textarea {
&:extend(input[type="text"]);
resize: vertical;
&:focus {
-webkit-appearance: none; /* iOS input madness */
outline: none; /* remove chrome blue outline */
}
}
select {
&:extend(input[type="text"]);
-webkit-appearance: none;
-moz-appearance: none;
background-image: url(/dist/images/icons/Icons_Select_Arrow.svg);
background-repeat: no-repeat;
background-position: center right 10px;
background-size: 12px;
&[size] {
background-image:none;
}
&::-ms-expand {
border: none;
color: transparent;
background: transparent url(/dist/images/icons/Icons_Select_Arrow.svg) no-repeat center;
background-size: 12px;
}
&:focus {
-webkit-appearance: none; /* iOS input madness */
outline: none; /* remove chrome blue outline */
}
}
/* RADIO & CHECKBOX LIST */
.radio-list,
.checkbox-list {
ul {
padding: 0;
}
li {
&:before {
display:none;
}
}
}
/* VALIDATION */
.input-error {
color:@bs-red;
}
/* PLACEHOLDER */
::-webkit-input-placeholder,
::-moz-placeholder,
:-ms-input-placeholder,
input:-moz-placeholder
{
color: fade(@bs-black, 50%);
}
.disabled {
opacity: 0.25;
pointer-events: none;
}_web-forms.less
_forms.less
Sexy code right...

Dealing with Web forms styling
Would have normally had to override the webform styles or create a less file in the sitecore modules folder
module.exports = {
// Less
main: {
options: {
sourceMap: true,
paths: ['<%= paths.cssSrc %>'] // scans for @import
},
files: [{
expand: true, // Enable dynamic expansion.
cwd: '<%= paths.cssSrc %>', // Src matches are relative to this path.
src: ['**/main.less', '**/print.less'], // Actual pattern(s) to match.
dest: '<%= paths.cssDist %>', // Destination path prefix.
ext: '.css', // Dest filepaths will have this extension.
}],
},
wffm: {
options: {
sourceMap: false,
paths: ['<%= paths.scModule %>']
},
files: {
'<%= paths.wffmDist %>/<%= paths.wffmCssFileName %>.css': '<%= paths.wffmSrc %>/_web-forms.less'
}
}
};Code snippet
Less build task

Solution explorer
Typography and Mixins
.define(@var) {
@header: 'h@{var}';
@header-mobile: 'mobile-h@{var}';
}
.h(@index) when (@index > 0) {
h@{index} {
.define(@index);
font-size: @base-font-size * @@header-mobile;
@media @tablet {
font-size: @base-font-size * @@header;
}
}
.h(@index - 1);
}
.h(0) {
}
.h(5);
Dissecting modules into core styles
- buttons
- forms
- gradients
- grid
- icons
- link list
- tables
Code snippet
@import (reference) "_breakpoints.less";
@import (reference) "_variables.less";
.btn {
height: 50px;
line-height: 52px;
min-width: 165px;
padding: 0 50px 0 20px;
border: none;
border-radius: 30px;
box-sizing: border-box;
display: inline-block;
text-align: left;
cursor: pointer;
margin-top: @division-small;
transition: opacity 0.25s @cubic-1;
&:hover,
&:focus {
opacity: 0.5;
}
&--base {
background: fade(@dark-grey, 30%) url(/dist/images/icons/Icons_Arrow.svg) no-repeat center right 20px;
background-size: 18px 18px;
}
&--cta {
height: 60px;
line-height: 62px;
background: @bs-magenta url(/dist/images/icons/Icons_Arrow_White.svg) no-repeat center right 20px;
color: @white;
background-size: 18px 18px;
}
&--ghost {
background: fade(@white, 30%) url(/dist/images/icons/Icons_Arrow_White.svg) no-repeat center right 20px;
color: @white;
background-size: 18px 18px;
}
}Lessons learnt
- Permissions
- dist issues (check out all the things)
- including images in the solution after copying through image task
- JavaScript debugging task needs to be introduced.
Feedback styling approach into Foundations

Do Frontend tools need to be in Foundations solution?
Could this be stored in GitHub? Or separated out into its own project and Nuget package?

Any questions?


Thank you
Frontend Technical Approach
By Matthew Neil
Frontend Technical Approach
Frontend technical approach simplified
- 422