JSFiddle - React, Tailwind, and code Playground

HTML

<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">
<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.js"></script>
<div ng-controller='Ctrl'>
    <h2>Plain version (nxEqual)</h2>
    <form name="form">
        <input ng-model="login.password" type="password" name="password" placeholder="Password" required><br>
        <div class="error" ng-show="form.password.$error.required">Required</div>
        <input ng-model="login.verify" type="password" name="verify" placeholder="Confirm Password" required nx-equal="login.password"><br>
        <div class="error" ng-show="form.verify.$error.required">Required!</div>
        <div class="error" ng-show="form.verify.$error.nxEqual">Must be equal!</div>
    </form>
            
    <h2>Extended version (nxEqualEx)</h2>
    <form name="formEx">
        <input ng-model="loginEx.password" type="password" name="password" placeholder="Password" required><br>
        <div class="error" ng-show="formEx.password.$error.required">Required</div>
        <input ng-model="loginEx.verify" type="password" name="verify" placeholder="Confirm Password" required nx-equal-ex="loginEx.password"><br>
        <div class="error" ng-show="formEx.verify.$error.required">Required!</div>
        <div class="error" ng-show="formEx.verify.$error.nxEqualEx">Must be equal!</div>
    </form>
</div>

CSS

body {
    margin: 10px;
}
h2 {
    margin: 20px 0 5px 0;
}
.error {
    color: darkred;
    margin: 2px 0 5px 0;
}

JavaScript

var app = angular.module('myApp', []);

app.directive('nxEqual', function() {
    return {
        require: 'ngModel',
        link: function (scope, elem, attrs, model) {
            if (!attrs.nxEqual) {
                console.error('nxEqual expects a model as an argument!');
                return;
            }
            scope.$watch(attrs.nxEqual, function (value) {
                model.$setValidity('nxEqual', value === model.$viewValue);
            });
            model.$parsers.push(function (value) {
                var isValid = value === scope.$eval(attrs.nxEqual);
                model.$setValidity('nxEqual', isValid);
                return isValid ? value : undefined;
            });
        }
    };
});

app.directive('nxEqualEx', function() {
    return {
        require: 'ngModel',
        link: function (scope, elem, attrs, model) {
            if (!attrs.nxEqualEx) {
                console.error('nxEqualEx expects a model as an argument!');
                return;
            }
            scope.$watch(attrs.nxEqualEx, function (value) {
                // Only compare values if the second ctrl has a value.
                if (model.$viewValue !== undefined && model.$viewValue !== '') {
                    model.$setValidity('nxEqualEx', value === model.$viewValue);
                }
            });
            model.$parsers.push(function (value) {
                // Mute the nxEqual error if the second ctrl is empty.
                if (value === undefined || value === '') {
                    model.$setValidity('nxEqualEx', true);
                    return value;
                }
                var isValid = value === scope.$eval(attrs.nxEqualEx);
                model.$setValidity('nxEqualEx', isValid);
                return isValid ? value : undefined;
            });
        }
    };
});

function Ctrl($scope) {}