diff --git a/src/Data/CSS/Preprocessor/Conditions/Expr.hs b/src/Data/CSS/Preprocessor/Conditions/Expr.hs
--- a/src/Data/CSS/Preprocessor/Conditions/Expr.hs
+++ b/src/Data/CSS/Preprocessor/Conditions/Expr.hs
@@ -72,7 +72,7 @@
 ---- Shunting Yard Evaluator
 --------
 -- | Dynamic types for evaluating media queries.
-data Datum = B Bool | N Float | Ratio Float Float deriving Eq
+data Datum = B Bool | N Float | R {minv :: Float, maxv :: Float} deriving Eq
 
 -- | Evaluates a media query with the given functions for evaluating vars & tokens.
 eval :: (Text -> Datum) -> (Token -> Datum) -> Expr -> Bool
@@ -87,12 +87,26 @@
 eval' stack v t (Tok tok:ops) | t tok /= B False = eval' (t tok:stack) v t ops
 eval' stack v t (Tok (Ident name):ops) = eval' (v name:stack) v t ops
 -- TODO: How should I handle ratios?
-eval' (N y:N x:stack) v t (MkRatio:ops) = eval' (Ratio x y:stack) v t ops
+eval' (N y:N x:stack) v t (MkRatio:ops) = eval' (N (x / y):stack) v t ops
 eval' (N y:N x:stack) v t (Less:ops) = eval' (B (x < y):stack) v t ops
+eval' (R _ y:N x:stack) v t (Less:ops) = eval' (B (x < y):stack) v t ops
+eval' (N y: R x _:stack) v t (Less:ops) = eval' (B (x < y):stack) v t ops
+eval' (R _ y:R x _:stack) v t (Less:ops) = eval' (B (x < y):stack) v t ops
 eval' (N y:N x:stack) v t (LessEq:ops) = eval' (B (x <= y):stack) v t ops
-eval' (y:x:stack) v t (Equal:ops) = eval' (B (x == y):stack) v t ops
+eval' (R _ y:N x:stack) v t (LessEq:ops) = eval' (B (x <= y):stack) v t ops
+eval' (N y: R x _:stack) v t (LessEq:ops) = eval' (B (x <= y):stack) v t ops
+eval' (R _ y:R x _:stack) v t (LessEq:ops) = eval' (B (x <= y):stack) v t ops
+eval' (R yMin yMax:R xMin xMax:stack) v t (Equal:ops) =
+    eval' (B (xMax >= yMin && xMin <= yMax) : stack) v t ops
+eval' (N y: N x:stack) v t (Equal:ops) = eval' (B (x == y):stack) v t ops
 eval' (N y:N x:stack) v t (Greater:ops) = eval' (B (x > y):stack) v t ops
+eval' (R y _:N x:stack) v t (Greater:ops) = eval' (B (x > y):stack) v t ops
+eval' (N y:R _ x:stack) v t (Greater:ops) = eval' (B (x > y):stack) v t ops
+eval' (R y _:R _ x:stack) v t (Greater:ops) = eval' (B (x > y):stack) v t ops
 eval' (N y:N x:stack) v t (GreaterEq:ops) = eval' (B (x >= y):stack) v t ops
+eval' (R y _:N x:stack) v t (GreaterEq:ops) = eval' (B (x >= y):stack) v t ops
+eval' (N y:R _ x:stack) v t (GreaterEq:ops) = eval' (B (x >= y):stack) v t ops
+eval' (R y _:R _ x:stack) v t (GreaterEq:ops) = eval' (B (x >= y):stack) v t ops
 eval' (B ret:_) _ _ [] = ret
 eval' [] _ _ [] = True -- Special case
 eval' _ _ _ _ = False -- Error handling fallback.
diff --git a/src/Data/CSS/Style/Selector/Index.hs b/src/Data/CSS/Style/Selector/Index.hs
--- a/src/Data/CSS/Style/Selector/Index.hs
+++ b/src/Data/CSS/Style/Selector/Index.hs
@@ -97,3 +97,4 @@
     hashWithSalt seed (Substring val) = seed `hashWithSalt` (4::Int) `hashWithSalt` unpack val
     hashWithSalt seed (Include val) = seed `hashWithSalt` (5::Int) `hashWithSalt` unpack val
     hashWithSalt seed (Dash val) = seed `hashWithSalt` (6::Int) `hashWithSalt` unpack val
+    hashWithSalt seed (Callback _) = seed `hashWithSalt` (7::Int)
diff --git a/src/Data/CSS/Style/Selector/Interpret.hs b/src/Data/CSS/Style/Selector/Interpret.hs
--- a/src/Data/CSS/Style/Selector/Interpret.hs
+++ b/src/Data/CSS/Style/Selector/Interpret.hs
@@ -14,7 +14,7 @@
 import Data.Bits (xor)
 
 -- For pseudoclasses
-import Data.CSS.Syntax.Selector (parseSelectors)
+import Data.CSS.Syntax.Selector (parseSelectors, PropertyFunc(..))
 import Data.CSS.Syntax.Tokens (Token(..), NumericValue(..))
 
 -- | A compiled(?) CSS selector.
@@ -81,6 +81,7 @@
 compileAttrTest (Substring val) = isInfixOf $ unpack val
 compileAttrTest (Include val) = hasWord $ unpack val
 compileAttrTest (Dash val) = hasLang $ unpack val
+compileAttrTest (Callback (PropertyFunc cb)) = cb
 
 sortAttrs :: [(Text, Maybe Text, b)] -> [(Text, Maybe Text, b)]
 sortAttrs = sortBy compareAttrs where compareAttrs (x, x', _) (y, y', _) = (x, x') `compare` (y, y')
diff --git a/src/Data/CSS/Syntax/Selector.hs b/src/Data/CSS/Syntax/Selector.hs
--- a/src/Data/CSS/Syntax/Selector.hs
+++ b/src/Data/CSS/Syntax/Selector.hs
@@ -1,7 +1,7 @@
 -- | Parses CSS selectors
 -- See `parseSelectors`
 module Data.CSS.Syntax.Selector(
-        Selector(..), SimpleSelector(..), PropertyTest(..),
+        Selector(..), SimpleSelector(..), PropertyTest(..), PropertyFunc(..),
         parseSelectors
     ) where
 
@@ -33,7 +33,15 @@
     | Substring Text -- ^ Matches whether the attribute contains the given value, e.g. "*="
     | Include Text -- ^ Is one of the whitespace-seperated values the one specified? e.g. "~="
     | Dash Text -- ^ Matches whitespace seperated values, or their "-"-seperated prefixes. e.g. "|="
+    | Callback PropertyFunc -- ^ Calls the given function to test this property.
     deriving (Show, Eq)
+-- | Caller-specified functions to extend property selection.
+-- Has incorrect Show/Eq implementations so this rare exception doesn't break things.
+data PropertyFunc = PropertyFunc (String -> Bool)
+instance Show PropertyFunc where
+    show _ = "xx"
+instance Eq PropertyFunc where
+    _ == _ = False
 
 -- | Parses a CSS selector.
 parseSelectors :: Parser [Selector]
diff --git a/stylist.cabal b/stylist.cabal
--- a/stylist.cabal
+++ b/stylist.cabal
@@ -10,7 +10,7 @@
 -- PVP summary:      +-+------- breaking API changes
 --                   | | +----- non-breaking API additions
 --                   | | | +--- code changes with no API change
-version:             2.2.0.0
+version:             2.3.0.0
 
 -- A short (one-line) description of the package.
 synopsis:            Apply CSS styles to a document tree.
