How to use Bootstrap or other CSS frameworks on a small part of a page without affecting the rest of the page's style
I'm currently working on a project where I have an AngularJS app (a form), that will be displayed on several different external websites. This means that I have to style the form to fit into several different page designs, with different existing stylesheets.
Published:
The form itself is styled using Bootstrap, one of the most commonly used front-end frameworks.
TL;DR: To style only a selected part of a website using a CSS framework, I create a container div around the app with a unique id, and then use LESS to change all the style definitions in the framework to only affect this particular id. This process is done automatically on build using .NET MVCs bundling framework.
What happens if I just include bootstrap.css on the external website?
This was my first attempt at styling the form. At the beginning of the javascript code for the app, I added the following code:
var linkElement = document.createElement("link");
linkElement.setAttribute("rel", "stylesheet");
linkElement.setAttribute("type", "text/css");
linkElement.setAttribute("href",
"//www.mysite.com/Styles/bootstrap/bootstrap.css");
document.getElementsByTagName("head")[0]
.appendChild(linkElement);
This bit of code adds an extra link tag to the head of the page, referencing the stylesheet I use for my form.
The problem is, that the loaded stylesheet affects everything on the page, and usually ruins the design of the site.
Here is one example of the styles in bootstrap.css:
a { color: #428bca; }
This sets all links to be blue. As you can see, there are no class names or ids on the style, so this style will affect link tags on the whole page, without any limits.
Separate the form from the rest of the page
To be able to create styles only for the form, I started by giving my app container a unique id:
<div id="ccapp-container" ng-app="CreditcardApp">
<div ng-view=""></div>
</div>
The id of the div containing the form is "ccapp-container". Now I can use this id in my CSS to only target that part of the page. For example, I could change the CSS mentioned earlier to this:
#ccapp-container a { color: #428bca; }
This will set the links inside the div with id="ccapp-container" to be blue, but no links elsewhere on the page is affected.
But then what? Bootstrap.css is more than 6000 lines of CSS code, so I can't possibly change all the styles by hand.
Use LESS to add the id to all styles in bootstrap.css
LESS is a kind of programming language for CSS. It enables you to create reusable modules and then compile these modules into CSS.
Using LESS, I can create my own version of bootstrap.css where all the styles contained in it are modified to only affect the div with this id.
I called this file "bootstrap-in-container.less":
#ccapp-container {
@import (less) "../../Styles/bootstrap/bootstrap.css";
@import (less) "../../Styles/bootstrap/bootstrap-theme.css";
}
Note that I have to specify that the CSS files should be treated as LESS files, by adding (less) after the import statement. When you import files with the .less suffix, that is not necessary. The reason I don't simply rename bootstrap.css to bootstrap.less, is because I want to use the downloaded version of bootstrap.css without any modifications at all, which makes it easier to update Bootstrap later.
Transform LESS files using BundleConfig
In a standard .NET MVC project, BundleConfig.cs is used to configure the project's bundling and minification of javascript and CSS files.
My BundleConfig.cs looks like this:
using System;
using System.Web.Optimization;
using BundleTransformer.Core.Bundles;
namespace CreditcardApp
{
public class BundleConfig
{
public const string Scripts =
"~/bundles/creditcard/styles";
public static void RegisterBundles(
BundleCollection bundles)
{
BundleCollection bundles = BundleTable.Bundles;
var styles = new CustomStyleBundle(Styles);
styles.Include("~/CreditcardApp/Styles/bootstrap-in-container.less");
bundles.Add(styles);
}
}
}
To load the transformed stylesheet, I change my script that loads the style to this:
var linkElement = document.createElement("link");
linkElement.setAttribute("rel", "stylesheet");
linkElement.setAttribute("type", "text/css");
linkElement.setAttribute("href",
"//www.mysite.com/bundles/creditcard/styles");
document.getElementsByTagName("head")[0]
.appendChild(linkElement);
If I have a look at the transformed stylesheet, and compare it to the plain bootstrap.css, I can see changes such as this:
/* Original CSS */
.navbar { position: relative; }
/* Transformed CSS */
#ccapp-container .navbar { position: relative; }
The same is true for every single element in the stylesheet, each one has an added #ccapp-container.
Override style settings for the html and body tags
This works well for almost all of bootstrap.css, but there are a couple of exceptions. All style directly targeting the body and html tags will not work after the transformation.
Here is one example:
/* Original CSS */
body { margin: 0; }
/* Transformed CSS */
#ccapp-container body { margin: 0; }
The CSS after the transformation does not work anymore, because the browser is now looking for a body tag within the container with id equal to "ccapp-container". This container does not contain any body tags and therefore this style does nothing.
To solve this, you need to create a LESS file where you add all the styles in bootstrap.css that target the html and body tags directly and replace those with #ccapp-container. Simply open bootstrap.css and search for "html" and "body" to find these instances.
I created a file called _overrides.less, which looks like this:
#ccapp-container {
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.42857143;
color: #333;
background-color: #fff;
}
#ccapp-container input[type="button"] {
-webkit-appearance: button;
cursor: pointer;
}
#ccapp-container input[disabled] {
cursor: default;
}
Then I change bootstrap-in-container.less to this, to include the _overrides.less file:
#ccapp-container {
@import (less) "../../Styles/bootstrap/bootstrap.css";
@import (less) "../../Styles/bootstrap/bootstrap-theme.css";
}
@import "_overrides.less";
All done
Now I have my own, customized version of Bootstrap that only affects my app, and not the rest of the page where the app is displayed. Since almost the whole process is automated using LESS and BundleConfig, it is really easy to update Bootstrap later.