25

I decided to try out Bootstrap 3 tonight for the first time. I noticed that, in order to get the default, pretty form field styling, you need to add a form-control class to each input, textarea, select, etc. This is a pain for those who dynamically generate their forms (e.g., using Django). Django allows you to set attributes of form fields, but to do so globally would require a nasty monkey patch or else very non-DRY code.

  1. Is there a way to avoid the requirement of this class, still retaining the basic form field styles?
  2. Is there a quick way (preferably non-JS) to address this otherwise?
orokusaki
  • 48,267
  • 47
  • 159
  • 244
  • **For other Django users who find this**, my eventual solution was to simply override Django's `Form.__init__`, delegate to `super`, then iterate `self.fields`, updating each's `widget.attrs.get('class', '')` to include `form-control`, if the widget was a `TextInput`, a `Select`, or a `Textarea`. If you do the same, make sure you add the `form-control` class, vs simply setting it (so that you don't remove existing classes that are added to a form's widget. – orokusaki Dec 06 '13 at 16:25
  • **For Django users:** [`django-widget-tweaks`](https://github.com/kmike/django-widget-tweaks) is also an acceptable solution. – Nathan Osman Feb 28 '14 at 07:54

3 Answers3

12

I wondered why the answer below got downvotes first. I found my answer about the form-group in stead of the form-control class. The class form-control adds many CSS rules.

You should try to control your form output in the first place: https://stackoverflow.com/a/8474452/1596547

If you can't you could try the same as below. Apply the same rules on your inputs instead of the form-control, like:

input {
  display: block;
  width: 100%;
  height: 34px;
  padding: 6px 12px;
  font-size: 14px;
  line-height: 1.428571429;
  color: #555555;
  vertical-align: middle;
  background-color: #ffffff;
  border: 1px solid #cccccc;
  border-radius: 4px;
  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
  -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
          transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
}

input:focus {
  border-color: #66afe9;
  outline: 0;
  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
}

Less

With LESS > 1.4 you can use :extend(), see: https://stackoverflow.com/a/15573240/1596547. You can use this for the above by adding a rule to your less files:

input {
  &:extend(.form-control all);
}

Also see: Why gives Grunt / Recess an error and Lessc not when compiling Bootstrap 3 RC1?

The form-group is an container-div around your input / label constructs it only adds a margin-bottom: 15px;. You could build your forms without it. For this reason it is not required.

With css you could make some workarounds. I don't think you can avoid Javascript always. I don't know the HTML-structure of your Django forms. I have used the example form from http://getbootstrap.com/css/#forms and strip the form-control containers. Then i "fix" the differences. NOTE i also add a <br> tag in front of the submit button.

See: http://bootply.com/73370

1) form-group adds a margin-bottom: 15px; to fix this i add this margin to the input tags:

input {
    margin-bottom: 15px; }

This works accept for the checkbox (and radio). The Bootstrap CSS defines input[type="radio"], input[type="checkbox"] { line-height: normal; margin: 4px 0 0; } which overrules (caused by CSS Specificity, see: http://coding.smashingmagazine.com/2007/07/27/css-specificity-things-you-should-know/ ) the css for input above.

So the final rule will be:

input, input[type="checkbox"]  {
    margin-bottom: 15px;
}

2) the label of the checkbox also differs. NOTE the checkbox don't have a surrounding form-control but a checkbox class in stead. The css rules for the label text are: .radio label, .checkbox label { cursor: pointer; display: inline; font-weight: normal; margin-bottom: 0; } i this case you can't use CSS only (the label is a parent of the input checkbox, and there is no parent selector in CSS, see: Is there a CSS parent selector?). With jQuery you can select the label and add a class:

$('input[type="checkbox"]').parent("label").addClass('checkboxlabel');

Now add this class to your CSS with the same rules as the .checkbox label:

.checkboxlabel
{
    cursor: pointer;
    display: inline;
    font-weight: normal;
    margin-bottom: 0;
} 

Now both forms look basically the same i think:

forms look the same Also read: Django Forms and Bootstrap - CSS classes and <divs>

Community
  • 1
  • 1
Bass Jobsen
  • 47,890
  • 16
  • 139
  • 218
  • Excellent, thanks! It never occurred to me to just override `as_p`. I think that'll be a lot simpler than having to override a bunch of widgets, which would then require me to override `formfield_for_dbfield` or something, etc. I was less interested in adding a bunch of CSS - even though my roots are in CSS, I'm trying to build my site with as little custom CSS as possible, so I consider `as_p` to be *the* best approach here. – orokusaki Aug 14 '13 at 15:04
  • 2
    @bass-jobsen You may not want to blanket all inputs with that styling, since `input[type=button]`, `input[type=radio]` and `input[type=checkbox]` do not benefit from the same styling. – Albert Bori Nov 03 '13 at 20:06
  • bootstrap also defines .form-control[disabled],.form-ontrol[readonly],textarea.form-control, .has-warning .form-control:focus and many others – Davi Fiamenghi Jan 07 '14 at 04:18
5

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:

  1. Install django-bootstrap3
  2. Add bootstrap3 to your INSTALLED_APPS:

    INSTALLED_APPS = (
        ...
        'bootstrap3',
        ...
    )
    
  3. Update your template:

    1. load bootstrap3
    2. replace your {{form.as_p}} to {% bootstrap3_form form %}

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.

Helgi
  • 5,368
  • 1
  • 29
  • 47
ornoone
  • 613
  • 5
  • 10
  • 1
    Excellent - I've made this the correct answer, even though I haven't tried the app. It appears that the app is well documented and supported, and offers a solution that doesn't require customization. Thanks. – orokusaki Jan 29 '15 at 16:51
  • Small correction required to your answer: 2. replace your {{form.as_p}} to {% bootstrap3_form form %} should be 2. replace your {{form.as_p}} to {% bootstrap_form form %} – Hexatonic Dec 22 '16 at 19:10
2

One thing I did was create a mixin that adds the form-control class to each widget.

class BootStrap3FormControlMixin(object):
    def __init__(self, *args, **kwargs):
        super(BootStrap3FormControlMixin, self).__init__(*args, **kwargs)

        for field in self.fields.values():
            _class = field.widget.attrs.get('class', '')
            field.widget.attrs.update({'class': u'%s %s' % (_class, 'form-control',)})
orokusaki
  • 48,267
  • 47
  • 159
  • 244
emperorcezar
  • 341
  • 1
  • 9
  • 1
    That's what I ended up doing as well, but see my comment under the original question (the note about adding vs simply setting), or you'll run into problems later when you want to add another class. I've made the appropriate edit to your code (also fixed formatting). – orokusaki Jan 05 '14 at 15:51