Less and Bootstrap: how to use a span3 (or spanX [any number]) class as a mixin?
New Answer (requires LESS 1.4.0)
What you actually desire is something known as extending in LESS and SASS terminology. For example, you want an HTML element (just an example)...
<div class="myclass"></div>
...to fully behave as if it had a span3
class from bootstrap added to it, but without actually adding that class in the HTML. This can be done in LESS 1.4.0 using :extend()
, but still not easily, mainly because of the dynamic class generation of bootstrap will not be picked up by :extend()
.
Here is an example. Assume this initial LESS code (not dynamically generated .span3
classes as bootstrap does):
.span3 {
width: 150px;
}
.someClass .span3 {
font-size: 12px;
}
.someOtherClass.span3 {
background: blue;
}
You add this LESS code in 1.4.0:
.myclass {
&:extend(.span3);
}
Which produces this CSS:
.span3,
.myclass {
width: 150px;
}
.someClass .span3 {
font-size: 12px;
}
.someOtherClass.span3 {
background: blue;
}
NOTE how it did not automatically extend the other instances of .span3
. This is different than SASS, but it only means you need to be a bit more explicit in extending. This has the advantage of avoiding excessive CSS code bloat.
To fully extend, simply add the all
keyword in the extend()
(this is updated from my original code, as I was unaware of the all
option):
.myclass {
&:extend(.span3 all);
}
Which produces this:
.span3,
.myclass {
width: 150px;
}
.someClass .span3,
.someClass .myclass {
font-size: 12px;
}
.someOtherClass.span3,
.someOtherClass.myclass {
background: blue;
}
That makes your .myclass
fully equivalent (in my example) to the .span3
class. What this means in your case, however, is that you need to redefine any dynamic class generations of bootstrap to be non-dynamic. Something like this:
.span3 {
.span(3);
}
This is so the :extend(.span3)
will find a hard coded class to extend to. This would need to be done for any selector string that dynamically uses .span@{index}
to add the .span3
.
Original Answer
This answer assumed you desired to mixin properties from a dynamically generated class (that is what I thought your issue was).
Okay, I believe I discovered your issue. First of all, bootstrap defines the .spanX
series of classes in the mixins.less
file, so you obviously need to be sure you are including that in your bootstrap load. However, I assume it is a given that you have those included already.
Main Problem
The main issue is how bootstrap is generating those now, through a dynamic class name in a loop. This is the loop that defines the .spanX
series:
.spanX (@index) when (@index > 0) {
.span@{index} { .span(@index); }
.spanX(@index - 1);
}
.spanX (0) {}
Currently, because the class name itself is being dynamically generated, it cannot be used as a mixin name. I don't know if this is a bug or merely a limitation of LESS, but I do know that at present time of writing, any dynamically generated class name does not function as a mixin name. Therefore, .span3
may be in the CSS code to put as a class in your HTML, but it is not directly available to access for mixin purposes.
The Fix
However, because of how they have structured the code, you can still get what you need, because as you can see above in the loop code, they use a true mixin itself to define the code for the .spanX
classes. Therefore, you should be able to do this:
.myclass {
.span(3);
// other rules...
}
The .span(3)
code is what the loop is using to populate the .span3
class, so calling it for your classes will give the same code that .span3
has. Specifically bootstrap has this defined in mixins.less
for that mixin:
.span (@columns) {
width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1));
*width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%);
}
So you will get the width
properties for the .span3
in your .myclass
.
Media queries with less and Bootstrap
You are going to run into a few major problems in this approach.
First, there are issues in that Bootstrap is dynamically generating the .spanX
series of classes, so they are not immediately accessible as mixins, as I note in this answer.
Second, some of Bootstrap's span
code is not set by the .spanX
series, but is done through an attribute selector of [class*="span"]
(see, as of this writing, line 602 of the mixins.less file; they are also extensively used in the responsive files for the @media
rules). This selector code would not be picked up by a .span12
inclusion of code, and if you are not putting .span12
in your code, then your code will not have those properties applied. So you need to work around that.
There may be other issues not immediately obvious, but the point is, if you want to use Bootstrap for grid purposes (especially responsive), it is probably best to use it as it is designed. If you do not, then it is best to go and copy the Bootstrap code and modify it as you desire to make your own reduced grid code (if your intent is to use less overall classes in your html).
How can I work around the need for Bootstrap 3's form-control class?
You realy should check a Django app that render all your Django forms as nice Boostrap forms, simply by using a tag in your template.
Its name is django-bootstrap3. Here's how you use it:
- Install django-bootstrap3
Add
bootstrap3
to yourINSTALLED_APPS
:INSTALLED_APPS = (
...
'bootstrap3',
...
)Update your template:
- load
bootstrap3
- replace your
{{form.as_p}}
to{% bootstrap3_form form %}
- load
before:
<form method="post" class="form-horizontal" action="" >
<div class="hidden-desktop">
<button type="submit" class="btn btn-primary"> Save</button>
</div>
{% csrf_token %}
{{ form.as_p }}
<div class="actions form-actions">
<button type="submit" class="btn btn-primary"> Save</button>
</div>
</form>
after:
{% extends "base.html" %}
{% load bootstrap3 %}
<form method="post" class="form-horizontal" action="" >
<div class="hidden-desktop">
<button type="submit" class="btn btn-primary">{% bootstrap_icon "ok" %} Save</button>
</div>
{% csrf_token %}
{% bootstrap_form form %}
<div class="actions form-actions">
<button type="submit" class="btn btn-primary">{% bootstrap_icon "ok" %} Save</button>
</div>
Nothing else to do. Nothing to update in you form. No JavaScript hacks.
Twitter bootstrap 3 custom multiple theme's
Cause you mention @brand-primary i wrote an example for it.
First i look up all uses of @brand-primary
./type.less: color: @brand-primary;
./type.less: color: darken(@brand-primary, 10%);
./variables.less:@brand-primary: #428bca;
./variables.less:@link-color: @brand-primary;
./variables.less:@component-active-bg: @brand-primary;
./variables.less:@btn-primary-bg: @brand-primary;
./variables.less:@pagination-active-bg: @brand-primary;
./variables.less:@label-primary-bg: @brand-primary;
./variables.less:@progress-bar-bg: @brand-primary;
./variables.less:@panel-primary-border: @brand-primary;
./variables.less:@panel-primary-heading-bg: @brand-primary;
@brand-primary will be only used in type.less once i copy this part to my new less file brand-primary.less:
.red-area
{
//type.less
.text-primary {
color: @brand-primary-red;
&:hover {
color: darken(@brand-primary-red, 10%);
}
}
}
Again i copy the rules with @link-color from different files to brand-primary.less and rename @link-color to @link-color-red. I copy the @link-hover-color cause this is defined by @link-hover-color: darken(@link-color, 15%);
in variables.less.
And yes you're right this will effect many styles and will be complex. For example @component-active-bg will not used to set any style direct. @component-active-bg defines the value for @dropdown-link-active-bg, @nav-pills-active-link-hover-bg and @list-group-active-bg in variables.less
You will have to repeat this for every variable based on brand-primary
In some cases like the @link-color in thumbnails.less you will have to copy only a smaller part like:
.red-area
{
.thumbnail {
// Add a hover state for linked versions only
a&:hover,
a&:focus,
a&.active {
border-color: @link-color-red;
}
}
}
In other cases extending could help you to reduce the code to copy, see: https://stackoverflow.com/a/15573240/1596547
Doing this your final less file will look like:
//
// Load core variables and mixins
// --------------------------------------------------
@import "variables.less";
@import "mixins.less";
@brand-primary-red: #ff0000;
@link-color-red: @brand-primary-red;
@link-hover-color-red: darken(@link-color-red, 15%);
@component-active-bg-red: @brand-primary-red;
@dropdown-link-active-bg-red: @component-active-bg-red;
@nav-pills-active-link-hover-bg-red: @component-active-bg-red;
@list-group-active-bg-red: @component-active-bg-red;
@list-group-active-border-red: @list-group-active-bg-red;
@btn-primary-bg-red: @brand-primary-red;
@btn-primary-border-red: darken(@btn-primary-bg-red, 5%);
@pagination-active-bg-red: @brand-primary-red;
@label-primary-bg-red: @brand-primary-red;
@progress-bar-bg-red: @brand-primary-red;
@panel-primary-border-red: @brand-primary-red;
@panel-primary-heading-bg-red: @brand-primary-red;
.red-area
{
//type.less
.text-primary {
color: @brand-primary-red;
&:hover {
color: darken(@brand-primary-red, 10%);
}
}
//scaffolding.less
// Links
a {
color: @link-color-red;
text-decoration: none;
&:hover,
&:focus {
color: @link-hover-color-red;
text-decoration: underline;
}
&:focus {
.tab-focus();
}
}
//thumbnails.less
.thumbnail {
// Add a hover state for linked versions only
a&:hover,
a&:focus,
a&.active {
border-color: @link-color-red;
}
}
//buttons.less
// Make a button look and behave like a link
.btn-link {
color: @link-color-red;
font-weight: normal;
cursor: pointer;
border-radius: 0;
&,
&:active,
&[disabled],
fieldset[disabled] & {
background-color: transparent;
.box-shadow(none);
}
&,
&:hover,
&:focus,
&:active {
border-color: transparent;
}
&:hover,
&:focus {
color: @link-hover-color-red;
text-decoration: underline;
background-color: transparent;
}
&[disabled],
fieldset[disabled] & {
&:hover,
&:focus {
color: @btn-link-disabled-color;
text-decoration: none;
}
}
}
//nav.less
// Open dropdowns
.open > a {
&,
&:hover,
&:focus {
background-color: @nav-link-hover-bg;
border-color: @link-color-red;
.caret {
border-top-color: @link-hover-color-red;
border-bottom-color: @link-hover-color-red;
}
}
}
//dropdowns.less
// Active state
.dropdown-menu > .active > a {
&,
&:hover,
&:focus {
color: @dropdown-link-active-color;
text-decoration: none;
outline: 0;
background-color: @dropdown-link-active-bg-red;
}
}
//nav.less
.nav-pills {
> li {
float: left;
// Links rendered as pills
> a {
border-radius: @nav-pills-border-radius;
}
+ li {
margin-left: 2px;
}
// Active state
&.active > a {
&,
&:hover,
&:focus {
color: @nav-pills-active-link-hover-color;
background-color: @nav-pills-active-link-hover-bg-red;
.caret {
border-top-color: @nav-pills-active-link-hover-color;
border-bottom-color: @nav-pills-active-link-hover-color;
}
}
}
}
}
//list-group.less
// Linked list items
a.list-group-item {
color: @list-group-link-color;
.list-group-item-heading {
color: @list-group-link-heading-color;
}
// Hover state
&:hover,
&:focus {
text-decoration: none;
background-color: @list-group-hover-bg;
}
// Active class on item itself, not parent
&.active,
&.active:hover,
&.active:focus {
z-index: 2; // Place active items above their siblings for proper border styling
color: @list-group-active-color;
background-color: @list-group-active-bg-red;
border-color: @list-group-active-border-red;
// Force color to inherit for custom content
.list-group-item-heading {
color: inherit;
}
.list-group-item-text {
color: lighten(@list-group-active-bg, 40%);
}
}
}
//buttons.less
.btn-primary {
.button-variant(@btn-primary-color; @btn-primary-bg-red; @btn-primary-border-red);
}
//pagination.less
.pagination {
display: inline-block;
padding-left: 0;
margin: @line-height-computed 0;
border-radius: @border-radius-base;
> li {
display: inline; // Remove list-style and block-level defaults
> a,
> span {
position: relative;
float: left; // Collapse white-space
padding: @padding-base-vertical @padding-base-horizontal;
line-height: @line-height-base;
text-decoration: none;
background-color: @pagination-bg;
border: 1px solid @pagination-border;
margin-left: -1px;
}
&:first-child {
> a,
> span {
margin-left: 0;
.border-left-radius(@border-radius-base);
}
}
&:last-child {
> a,
> span {
.border-right-radius(@border-radius-base);
}
}
}
> li > a,
> li > span {
&:hover,
&:focus {
background-color: @pagination-hover-bg;
}
}
> .active > a,
> .active > span {
&,
&:hover,
&:focus {
z-index: 2;
color: @pagination-active-color;
background-color: @pagination-active-bg-red;
border-color: @pagination-active-bg-red;
cursor: default;
}
}
> .disabled {
> span,
> span:hover,
> span:focus,
> a,
> a:hover,
> a:focus {
color: @pagination-disabled-color;
background-color: @pagination-bg;
border-color: @pagination-border;
cursor: not-allowed;
}
}
}
//labels.less
.label-primary {
.label-variant(@label-primary-bg-red);
}
//progress-bars.less
// Bar of progress
.progress-bar {
background-color: @progress-bar-bg-red;
}
//panels.less
.panel-primary {
.panel-variant(@panel-primary-border-red; @panel-primary-text; @panel-primary-heading-bg-red; @panel-primary-border-red);
}
}
You could compile this file like theme.less or remove the imports and import it in boostrap.less
The final css will look like:
.red-area .text-primary {
color: #ff0000;
}
.red-area .text-primary:hover {
color: #cc0000;
}
.red-area a {
color: #ff0000;
text-decoration: none;
}
.red-area a:hover,
.red-area a:focus {
color: #b30000;
text-decoration: underline;
}
.red-area a:focus {
outline: thin dotted #333;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
a.red-area .thumbnail:hover,
a.red-area .thumbnail:focus,
a.red-area .thumbnail.active {
border-color: #ff0000;
}
.red-area .btn-link {
color: #ff0000;
font-weight: normal;
cursor: pointer;
border-radius: 0;
}
.red-area .btn-link,
.red-area .btn-link:active,
.red-area .btn-link[disabled],
fieldset[disabled] .red-area .btn-link {
background-color: transparent;
-webkit-box-shadow: none;
box-shadow: none;
}
.red-area .btn-link,
.red-area .btn-link:hover,
.red-area .btn-link:focus,
.red-area .btn-link:active {
border-color: transparent;
}
.red-area .btn-link:hover,
.red-area .btn-link:focus {
color: #b30000;
text-decoration: underline;
background-color: transparent;
}
.red-area .btn-link[disabled]:hover,
fieldset[disabled] .red-area .btn-link:hover,
.red-area .btn-link[disabled]:focus,
fieldset[disabled] .red-area .btn-link:focus {
color: #999999;
text-decoration: none;
}
.red-area .open > a,
.red-area .open > a:hover,
.red-area .open > a:focus {
background-color: #eeeeee;
border-color: #ff0000;
}
.red-area .open > a .caret,
.red-area .open > a:hover .caret,
.red-area .open > a:focus .caret {
border-top-color: #b30000;
border-bottom-color: #b30000;
}
.red-area .dropdown-menu > .active > a,
.red-area .dropdown-menu > .active > a:hover,
.red-area .dropdown-menu > .active > a:focus {
color: #ffffff;
text-decoration: none;
outline: 0;
background-color: #ff0000;
}
.red-area .nav-pills > li {
float: left;
}
.red-area .nav-pills > li > a {
border-radius: 4px;
}
.red-area .nav-pills > li + li {
margin-left: 2px;
}
.red-area .nav-pills > li.active > a,
.red-area .nav-pills > li.active > a:hover,
.red-area .nav-pills > li.active > a:focus {
color: #ffffff;
background-color: #ff0000;
}
.red-area .nav-pills > li.active > a .caret,
.red-area .nav-pills > li.active > a:hover .caret,
.red-area .nav-pills > li.active > a:focus .caret {
border-top-color: #ffffff;
border-bottom-color: #ffffff;
}
.red-area a.list-group-item {
color: #555555;
}
.red-area a.list-group-item .list-group-item-heading {
color: #333333;
}
.red-area a.list-group-item:hover,
.red-area a.list-group-item:focus {
text-decoration: none;
background-color: #f5f5f5;
}
.red-area a.list-group-item.active,
.red-area a.list-group-item.active:hover,
.red-area a.list-group-item.active:focus {
z-index: 2;
color: #ffffff;
background-color: #ff0000;
border-color: #ff0000;
}
.red-area a.list-group-item.active .list-group-item-heading,
.red-area a.list-group-item.active:hover .list-group-item-heading,
.red-area a.list-group-item.active:focus .list-group-item-heading {
color: inherit;
}
.red-area a.list-group-item.active .list-group-item-text,
.red-area a.list-group-item.active:hover .list-group-item-text,
.red-area a.list-group-item.active:focus .list-group-item-text {
color: #e1edf7;
}
.red-area .btn-primary {
color: #ffffff;
background-color: #ff0000;
border-color: #357ebd;
}
.red-area .btn-primary:hover,
.red-area .btn-primary:focus,
.red-area .btn-primary:active,
.red-area .btn-primary.active,
.open .dropdown-toggle.red-area .btn-primary {
color: #ffffff;
background-color: #d60000;
border-color: #285e8e;
}
.red-area .btn-primary:active,
.red-area .btn-primary.active,
.open .dropdown-toggle.red-area .btn-primary {
background-image: none;
}
.red-area .btn-primary.disabled,
.red-area .btn-primary[disabled],
fieldset[disabled] .red-area .btn-primary,
.red-area .btn-primary.disabled:hover,
.red-area .btn-primary[disabled]:hover,
fieldset[disabled] .red-area .btn-primary:hover,
.red-area .btn-primary.disabled:focus,
.red-area .btn-primary[disabled]:focus,
fieldset[disabled] .red-area .btn-primary:focus,
.red-area .btn-primary.disabled:active,
.red-area .btn-primary[disabled]:active,
fieldset[disabled] .red-area .btn-primary:active,
.red-area .btn-primary.disabled.active,
.red-area .btn-primary[disabled].active,
fieldset[disabled] .red-area .btn-primary.active {
background-color: #ff0000;
border-color: #357ebd;
}
.red-area .pagination {
display: inline-block;
padding-left: 0;
margin: 20px 0;
border-radius: 4px;
}
.red-area .pagination > li {
display: inline;
}
.red-area .pagination > li > a,
.red-area .pagination > li > span {
position: relative;
float: left;
padding: 6px 12px;
line-height: 1.428571429;
text-decoration: none;
background-color: #ffffff;
border: 1px solid #dddddd;
margin-left: -1px;
}
.red-area .pagination > li:first-child > a,
.red-area .pagination > li:first-child > span {
margin-left: 0;
border-bottom-left-radius: 4px;
border-top-left-radius: 4px;
}
.red-area .pagination > li:last-child > a,
.red-area .pagination > li:last-child > span {
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
}
.red-area .pagination > li > a:hover,
.red-area .pagination > li > span:hover,
.red-area .pagination > li > a:focus,
.red-area .pagination > li > span:focus {
background-color: #eeeeee;
}
.red-area .pagination > .active > a,
.red-area .pagination > .active > span,
.red-area .pagination > .active > a:hover,
.red-area .pagination > .active > span:hover,
.red-area .pagination > .active > a:focus,
.red-area .pagination > .active > span:focus {
z-index: 2;
color: #ffffff;
background-color: #ff0000;
border-color: #ff0000;
cursor: default;
}
.red-area .pagination > .disabled > span,
.red-area .pagination > .disabled > span:hover,
.red-area .pagination > .disabled > span:focus,
.red-area .pagination > .disabled > a,
.red-area .pagination > .disabled > a:hover,
.red-area .pagination > .disabled > a:focus {
color: #999999;
background-color: #ffffff;
border-color: #dddddd;
cursor: not-allowed;
}
.red-area .label-primary {
background-color: #ff0000;
}
.red-area .label-primary[href]:hover,
.red-area .label-primary[href]:focus {
background-color: #cc0000;
}
.red-area .progress-bar {
background-color: #ff0000;
}
.red-area .panel-primary {
border-color: #ff0000;
}
.red-area .panel-primary > .panel-heading {
color: #ffffff;
background-color: #ff0000;
border-color: #ff0000;
}
.red-area .panel-primary > .panel-heading + .panel-collapse .panel-body {
border-top-color: #ff0000;
}
.red-area .panel-primary > .panel-heading > .dropdown .caret {
border-color: #ffffff transparent;
}
.red-area .panel-primary > .panel-footer + .panel-collapse .panel-body {
border-bottom-color: #ff0000;
}
The css rules will be applied on everthing wrapped inside a .red-area class.
Notes:
- the less code could be optimalized.
- when copy all pieces by hand it won't be easy to handle updates of Bootstrap
Also read this answer to fit the best way of extending / copy classes: https://stackoverflow.com/a/19980145/1596547
CSS issue on Twitter Typeahead with Bootstrap 3
EDIT: Updated for Bootstrap 3.0
EDIT 2: Typeahead call was modified. See new jsfiddle
After playing around with the styling it looks like the form-control class doesn't quite line-up with the tt-hint. So I made sure the margins and borders line up. Taking Hieu Nguyen's answer and adding border-radius and support for input-small/input-large
CSS
.twitter-typeahead .tt-hint
{
display: block;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.428571429;
border: 1px solid transparent;
border-radius:4px;
}
.twitter-typeahead .hint-small
{
height: 30px;
padding: 5px 10px;
font-size: 12px;
border-radius: 3px;
line-height: 1.5;
}
.twitter-typeahead .hint-large
{
height: 45px;
padding: 10px 16px;
font-size: 18px;
border-radius: 6px;
line-height: 1.33;
}
Script for input-small/input-large
$('.typeahead.input-sm').siblings('input.tt-hint').addClass('hint-small');
$('.typeahead.input-lg').siblings('input.tt-hint').addClass('hint-large');
Updated jsfiddle: http://jsfiddle.net/KrtB5/542/
Related Topics
CSS - First Child Without Certain Class
How to Add a Custom Font to Rails App
How to Delay the Start of a CSS Animation
Assign CSS Attributes According to Class "Range"
Google Webfonts Render Choppy in Chrome on Windows
Differencebetween Pseudo-Classes and Pseudo-Elements
CSS Underline Less Than Width of Headline
:Nth-Letter Pseudo-Element Is Not Working
Reordering Divs Responsively with Twitter Bootstrap
What Is the Most Practical Way to Check for "@Supports" Support Using Only CSS
Possible to Achieve This Mobile/Desktop Layout Using Bootstrap? (Or Other Grid)