When you say "not all databases support this", I think a better way of putting it is the following :
Every major database supports this, as they support triggers, functions and other advanced features extensively.
This brings us to the conclusion that this is part of advanced SQL and makes sense at some point.
Do people actually use domains in their database designs?
The less possible, due to the extensive coverage required (considering operators, indexes, etc.)
If so to what extent?
Again, as limited as can be, if an existing type combined with a little additional defined logic (i.e. checks etc.) can do the trick, why go that far ?
How useful are they?
A whole lot. Let's consider for one second a not-so-good DBMS like MySQL, which I picked for this example for one reason : it lacks good support for the inet (IP address) type.
Now you want to write an application that is mostly focused on IP data like ranges and all that, and you're stuck with the default type and its limited functionality, you will either write additional functions and operators (like those supported natively in postgreSQL for example) or write much more complex queries for every functionality you need.
This is a case where you will easily justify the time spent on defining your own functions (inet >> inet in PostgreSQL : range contained in range operator).
At that point, you have already justified extending datatype support, there is only one other step to defining a new datatype.
Now back to PostgreSQL which has real nice type support but no unsigned int .. which you need, because you're really concerned about storage / performance (who knows ...), well you'll need to add it as well as the operators - although of course this is mostly deriving existing int operators.
What pitfalls have you encountered?
I don't play around with that as so far I haven't had a project that both required and justified the time required for this.
The biggest issues I can see coming with that would be reinventing the wheel, introducing bugs in the "safe" layer (db), incomplete type support which you'll only realize months later when your CONCAT(cast * AS varchar) fails because you did not define a cast(newtype as varchar), etc.
There are answers talking about "uncommon" etc. Definitely these are and should be uncommon (otherwise it means the dbms lacks a lot of important types), but on the other hand, one should remember that a (good) db is ACID compliant (unlike an application) and that anything related to consistency is better kept in there.
There are many cases where business logic is handled in the software layer and it could be done in SQL, where it's safer. App developers tend to feel more comfortable within the application layer and often avoid better solutions implemented in SQL, this should not be considered as good practice.
UDT's can be a good solution for optimization, a good example is given in another answer about the m/f type using char(1). If it had been a UDT it could be a boolean instead (unless we want to offer the third and fourth options). Of course we all know this isn't really optimization because of the column overhead, but the possibility is there.