Having said that, there are actually two right ways, of which the better one is hardly ever used!
One thing I have not done is put up a positive OK indication by fields when they pass validation. My feeling is that this could be misleading, as one only really knows the field is OK after the server validation is complete. But in some circumstances it might be a good idea.
One other thing I have not done, which is rarely if ever a good idea, is to force the user to complete fields in a particular order, or to prevent him/her from leaving a field until it is correct. I do use the focus function to assist the user, but he/she is free to move to a different field if desired.
There is one small problem that a couple of correspondents have pointed out to me. There is a bug in Internet Explorer (all versions, as far as I know) whereby the onChange event does not fire if the user makes use of auto-completion. There doesn't seem to be a good way around this. (I have found a couple of sites referring to the non-standard onPropertyChange event, but this fires on every keystroke, so is not of any use.)
The only option seems to be, if in a particular case this is likely to be seriously confusing to the user, to switch auto-completion off with the non-standard autocomplete attribute on the form. Or perhaps one could use IE Conditional Comments to at least give IE users a warning message.
On only one point do I have strong feelings: input should never be rejected due to the presence of leading or trailing white-space. It may be difficult for the user to see that the white-space is present, and failing to strip it off before validation is pure laziness on the part of the programmer. (This includes line-breaks, which may inadvertently be included if the user copies text from elsewhere; using ‘\s’ within a regular expression will look after this.)
There is of course much more to be said about validation in general. Witness the site designers who insist that credit-card start-date is a compulsory field, although some credit cards (including mine) do not have a start date. But this page concentrates specifically on the client-side aspects, so I'll leave the rest for another time.
Following the principles here will result in forms that are both more widely usable and more user-friendly than many of the forms on the Web today. Let's try to make the Web a better place.
Update, November 2004: another user requested an example of how to handle checkboxes. That seemed a good idea, so I have added a sequel page on that subject.
Update, June 2005: I've finally found a work-around for the fact that Internet Explorer doesn't (always/ever?) set the focus correctly. It turns out to be a timing bug in IE. I've inserted a 0.1 second delay in the focus setting and now it works correctly.
Update, February 2006: I’ll just mention here a point which confused a couple of correspondents: the table cell which receives the error message must always contain some text (otherwise it doesn’t get constructed properly in the DOM). If you don’t want to display any text, insert a non-breaking space ( ).
This page now has a Bulgarian translation, with thanks to Albert Ward.