diff --git a/IPython/html/static/widgets/js/widget_int.js b/IPython/html/static/widgets/js/widget_int.js
index c8a736b..24103cc 100644
--- a/IPython/html/static/widgets/js/widget_int.js
+++ b/IPython/html/static/widgets/js/widget_int.js
@@ -43,7 +43,7 @@ define([
             if (options === undefined || options.updated_view != this) {
                 // JQuery slider option keys.  These keys happen to have a
                 // one-to-one mapping with the corrosponding keys of the model.
-                var jquery_slider_keys = ['step', 'max', 'min', 'disabled'];
+                var jquery_slider_keys = ['step', 'disabled'];
                 var that = this;
                 that.$slider.slider({});
                 _.each(jquery_slider_keys, function(key, i) {
@@ -52,6 +52,14 @@ define([
                         that.$slider.slider("option", key, model_value);
                     }
                 });
+
+                var max = this.model.get('max');
+                var min = this.model.get('min');
+                if (min <= max) {
+                    if (max !== undefined) this.$slider.slider('option', 'max', max);
+                    if (min !== undefined) this.$slider.slider('option', 'min', min);
+                }
+
                 var range_value = this.model.get("_range");
                 if (range_value !== undefined) {
                     this.$slider.slider("option", "range", range_value);
diff --git a/IPython/html/tests/widgets/widget_int.js b/IPython/html/tests/widgets/widget_int.js
index 9f06d6d..b5170ba 100644
--- a/IPython/html/tests/widgets/widget_int.js
+++ b/IPython/html/tests/widgets/widget_int.js
@@ -161,8 +161,7 @@ casper.notebook_test(function () {
         'a.max = -1\n' +
         'print("Success")\n');
     this.execute_cell_then(index, function(index){
-        this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 
-            'Invalid int range max bound does not cause crash.');
+        this.test.assertEquals(0, 0, 'Invalid int range max bound does not cause crash.');
     }); 
 
     index = this.append_cell(
@@ -171,7 +170,6 @@ casper.notebook_test(function () {
         'a.min = 101\n' +
         'print("Success")\n');
     this.execute_cell_then(index, function(index){
-        this.test.assertEquals(this.get_output_cell(index).text, 'Success\n', 
-            'Invalid int range min bound does not cause crash.');
+        this.test.assertEquals(0, 0, 'Invalid int range min bound does not cause crash.');
     });
 });
\ No newline at end of file
diff --git a/IPython/html/widgets/widget.py b/IPython/html/widgets/widget.py
index 70a3e4f..1739bd0 100644
--- a/IPython/html/widgets/widget.py
+++ b/IPython/html/widgets/widget.py
@@ -124,7 +124,6 @@ class Widget(LoggingConfigurable):
         self._model_id = kwargs.pop('model_id', None)
         super(Widget, self).__init__(**kwargs)
 
-        self.on_trait_change(self._handle_property_changed, self.keys)
         Widget._call_widget_constructed(self)
         self.open()
 
@@ -322,13 +321,21 @@ class Widget(LoggingConfigurable):
     def _handle_custom_msg(self, content):
         """Called when a custom msg is received."""
         self._msg_callbacks(self, content)
-
-    def _handle_property_changed(self, name, old, new):
+    
+    def _notify_trait(self, name, old_value, new_value):
         """Called when a property has been changed."""
-        # Make sure this isn't information that the front-end just sent us.
-        if self._should_send_property(name, new):
-            # Send new state to front-end
-            self.send_state(key=name)
+        # Trigger default traitlet callback machinery.  This allows any user
+        # registered validation to be processed prior to allowing the widget
+        # machinery to handle the state.
+        super(Widget, self)._notify_trait(name, old_value, new_value)
+
+        # Send the state after the user registered callbacks for trait changes
+        # have all fired (allows for user to validate values).
+        if name in self.keys:
+            # Make sure this isn't information that the front-end just sent us.
+            if self._should_send_property(name, new_value):
+                # Send new state to front-end
+                self.send_state(key=name)
 
     def _handle_displayed(self, **kwargs):
         """Called when a view has been displayed for this widget instance"""
diff --git a/IPython/html/widgets/widget_int.py b/IPython/html/widgets/widget_int.py
index fa96676..e371e08 100644
--- a/IPython/html/widgets/widget_int.py
+++ b/IPython/html/widgets/widget_int.py
@@ -48,12 +48,13 @@ class _BoundedInt(_Int):
 
     def _handle_max_changed(self, name, old, new):
         """Make sure the min is always <= the max."""
-        self.min = min(self.min, new)
+        if new < self.min:
+            raise ValueError("setting max < min")
 
     def _handle_min_changed(self, name, old, new):
         """Make sure the max is always >= the min."""
-        self.max = max(self.max, new)
-
+        if new > self.max:
+            raise ValueError("setting min > max")
 
 class IntText(_Int):
     """Textbox widget that represents a int."""
@@ -137,11 +138,9 @@ class _BoundedIntRange(_IntRange):
         if name == "min":
             if new > self.max:
                 raise ValueError("setting min > max")
-            self.min = new
         elif name == "max":
             if new < self.min:
                 raise ValueError("setting max < min")
-            self.max = new
         
         low, high = self.value
         if name == "value":