By Christian Oudard

2008-11-18 13:45:09 8 Comments

I would like to get all descendant text nodes of an element, as a jQuery collection. What is the best way to do that?


@Salman A 2014-01-19 10:29:22

jQuery.contents() can be used with jQuery.filter to find all child text nodes. With a little twist, you can find grandchildren text nodes as well. No recursion required:

$(function() {
  var $textNodes = $("#test, #test *").contents().filter(function() {
    return this.nodeType === Node.TEXT_NODE;
   * for testing
  $textNodes.each(function() {
div { margin-left: 1em; }
<script src=""></script>

<div id="test">
  child text 1<br>
  child text 2
    grandchild text 1
    <div>grand-grandchild text 1</div>
    grandchild text 2
  child text 3<br>
  child text 4


@Amanda 2020-08-06 04:27:52

I tried this. It prints tag names out of order. Is there a way to print tag names in the order they occur? I asked a separate question here…

@Tim Down 2010-12-09 15:13:26

jQuery doesn't have a convenient function for this. You need to combine contents(), which will give just child nodes but includes text nodes, with find(), which gives all descendant elements but no text nodes. Here's what I've come up with:

var getTextNodesIn = function(el) {
    return $(el).find(":not(iframe)").addBack().contents().filter(function() {
        return this.nodeType == 3;


Note: If you're using jQuery 1.7 or earlier, the code above will not work. To fix this, replace addBack() with andSelf(). andSelf() is deprecated in favour of addBack() from 1.8 onwards.

This is somewhat inefficient compared to pure DOM methods and has to include an ugly workaround for jQuery's overloading of its contents() function (thanks to @rabidsnail in the comments for pointing that out), so here is non-jQuery solution using a simple recursive function. The includeWhitespaceNodes parameter controls whether or not whitespace text nodes are included in the output (in jQuery they are automatically filtered out).

Update: Fixed bug when includeWhitespaceNodes is falsy.

function getTextNodesIn(node, includeWhitespaceNodes) {
    var textNodes = [], nonWhitespaceMatcher = /\S/;

    function getTextNodes(node) {
        if (node.nodeType == 3) {
            if (includeWhitespaceNodes || nonWhitespaceMatcher.test(node.nodeValue)) {
        } else {
            for (var i = 0, len = node.childNodes.length; i < len; ++i) {

    return textNodes;


@crosenblum 2011-02-10 15:56:23

Can the element passed in, be the name of a div?

@Tim Down 2011-02-10 16:43:42

@crosenblum: You could call document.getElementById() first, if that's what you mean: var div = document.getElementById("foo"); var textNodes = getTextNodesIn(div);

@bobpoekert 2012-02-03 00:29:33

Because of a bug in jQuery if you have any iframes in el you'll need to use .find(':not(iframe)') instead of .find('*') .

@Robin Maben 2012-02-06 11:52:17

@rabidsnail: I think, the use of .contents() anyways implies it will search through the iframe as well. I don't see how it could be a bug.

@bobpoekert 2012-02-13 21:31:11 Whether this is actually a bug seems to be up for debate, but bug or not if you call find('*').contents() on a node that contains an iframe which hasn't been added to the dom you'll get an exception at an undefined point.

@Tim Down 2012-02-13 21:57:26

@rabidsnail: OK, I think that's at least an annoyance (if not a bug) in jQuery and a point in favour of the plain DOM version. I'll edit my answer. Thanks.

@Mottie 2013-02-14 14:03:57

andSelf() was deprecated in jQuery 1.8, you can use addBack() instead.

@ErikE 2013-11-18 23:29:41

You could consider nonWhitespace = /\S/ and if (includeWhitespaceNodes || nonWhitespace.test(node.nodeValue)) { which at least boasts greater simplicity (though it would respond differently to empty text nodes, if those are possible). I also think there could be improvement in the regex variable name... something like whitespaceMatcher or something to indicate what the variable is.

@Tim Down 2013-11-18 23:42:10

@ErikE: I like descriptive variable names. I have a feeling I picked whitespace to avoid the code having horizontal scrollbars on my browser.

@Tim Down 2013-11-18 23:55:05

@ErikE: I agree with you on both counts and have edited my answer. Empty text nodes are indeed possible but will be treated the same by both !/^\s*$/.test() and /\S/.test() so there's no problem there.

@ErikE 2013-11-19 00:03:06

oh, right, it was * not + so empty nodes were matched before. Glad you liked my suggestions!

@Ben Siver 2014-03-28 14:58:27

Great suggestion. I would recommend using Node.TextNode in place of 3 for better readability.

@Tim Down 2014-03-28 16:31:54

@BenS: I would but Node.TEXT_NODE isn't supported in IE <= 8.

@Brian Geihsler 2014-06-11 18:55:05

This code has a bug in it. Right now when you pass false for including whitespace, it ONLY modifies whitespace nodes instead of excluding them. The line if (includeWhitespaceNodes || !nonWhitespaceMatcher.test(node.nodeValue)) should instead read: if (includeWhitespaceNodes || nonWhitespaceMatcher.test(node.nodeValue)).

@Tim Down 2014-06-11 22:35:09

@BrianGeihsler: You're right, thanks. I simplified the regular expression last November but failed to negate the condition. Wish I'd tested it now.

@Amanda 2020-08-06 04:16:42

@TimDown I tried your method but it gives the nodes out of order. What must be done to have tags in order? I asked a separate question here…

@Tim Down 2020-08-06 08:05:36

@Amanda: I think the non-jQuery version will give you nodes in document order.

@Amanda 2020-08-06 09:01:08

@TimDown You mean the jquery version? I am using cheerio and I am not getting so.

@Amanda 2020-08-06 09:17:06

@TimDown I came across an answer by AKX at… . This seems to work. How is it different from what you proposed in the answer? Could you please explain/help?

@Alex W 2014-10-13 18:03:57

I was getting a lot of empty text nodes with the accepted filter function. If you're only interested in selecting text nodes that contain non-whitespace, try adding a nodeValue conditional to your filter function, like a simple $.trim(this.nodevalue) !== '':

        return this.nodeType === 3 && $.trim(this.nodeValue) !== '';

Or to avoid strange situations where the content looks like whitespace, but is not (e.g. the soft hyphen &shy; character, newlines \n, tabs, etc.), you can try using a Regular Expression. For example, \S will match any non-whitespace characters:

            return this.nodeType === 3 && /\S/.test(this.nodeValue);

@Amanda 2020-08-06 04:28:08

I tried this. It prints tag names out of order. Is there a way to print tag names in the order they occur? I asked a separate question here…

@iConnor 2014-02-16 05:16:35

For some reason contents() didn't work for me, so if it didn't work for you, here's a solution I made, I created jQuery.fn.descendants with the option to include text nodes or not


Get all descendants including text nodes and element nodes


Get all descendants returning only text nodes


Get all descendants returning only element nodes


Coffeescript Original:

jQuery.fn.descendants = ( textNodes ) ->

    # if textNodes is 'all' then textNodes and elementNodes are allowed
    # if textNodes if true then only textNodes will be returned
    # if textNodes is not provided as an argument then only element nodes
    # will be returned

    allowedTypes = if textNodes is 'all' then [1,3] else if textNodes then [3] else [1]

    # nodes we find
    nodes = []

    dig = (node) ->

        # loop through children
        for child in node.childNodes

            # push child to collection if has allowed type
            nodes.push(child) if child.nodeType in allowedTypes

            # dig through child if has children
            dig child if child.childNodes.length

    # loop and dig through nodes in the current
    # jQuery object
    dig node for node in this

    # wrap with jQuery
    return jQuery(nodes)

Drop In Javascript Version

var __indexOf=[].indexOf||function(e){for(var t=0,n=this.length;t<n;t++){if(t in this&&this[t]===e)return t}return-1}; /* indexOf polyfill ends here*/ jQuery.fn.descendants=function(e){var t,n,r,i,s,o;t=e==="all"?[1,3]:e?[3]:[1];i=[];n=function(e){var r,s,o,u,a,f;u=e.childNodes;f=[];for(s=0,o=u.length;s<o;s++){r=u[s];if(a=r.nodeType,,a)>=0){i.push(r)}if(r.childNodes.length){f.push(n(r))}else{f.push(void 0)}}return f};for(s=0,o=this.length;s<o;s++){r=this[s];n(r)}return jQuery(i)}

Unminified Javascript version:

This is cross browser, a small Array.indexOf polyfill is included in the code.

@Guillermo 2011-07-30 17:47:35

I had the same problem and solved it with:


$.fn.nextNode = function(){
  var contents = $(this).parent().contents();
  return contents.get(contents.index(this)+1);



Is like next() but also returns the text nodes.

@Guillermo 2012-02-14 10:15:18

.nextSibling is from Dom specification:…

@Mr_Green 2013-06-19 11:24:08

Can also be done like this:

var textContents = $(document.getElementById("ElementId").childNodes).filter(function(){
        return this.nodeType == 3;

The above code filters the textNodes from direct children child nodes of a given element.

@Tim Down 2013-10-17 21:15:54

... but not all the descendant child nodes (e.g. a text node that is the child of an element that is a child of the original element).

@davenpcj 2013-08-23 14:41:21

For me, plain old .contents() appeared to work to return the text nodes, just have to be careful with your selectors so that you know they will be text nodes.

For example, this wrapped all the text content of the TDs in my table with pre tags and had no problems.

jQuery("#resultTable td").content().wrap("<pre/>")

@He Nrik 2011-10-19 16:07:40

$('body').find('*').contents().filter(function () { return this.nodeType === 3; });

@Christian Oudard 2008-11-18 13:47:45

Jauco posted a good solution in a comment, so I'm copying it here:

  .filter(function() {
    return this.nodeType === 3; //Node.TEXT_NODE

@Jauco 2009-07-11 13:53:14

actually $(elem) .contents() .filter(function() { return this.nodeType == Node.TEXT_NODE; }); is enough

@Christian Oudard 2009-12-29 20:00:54

IE7 doesn't define the Node global, so you have to use this.nodeType == 3, unfortunately:

@Tim Down 2010-10-15 14:12:28

Does this not only return the text nodes that are the direct children of the element rather than descendants of the element as the OP requested?

@Tim Down 2012-10-10 22:35:53

I've just noticed that your first answer from 2008 was almost exactly what I independently came up with much later. Why did you edit it?

@Shahar 2013-09-29 08:14:35

add .text() at the end if you want it so be a string. Otherwise it's still an object. Trying to show it in the document will end up displaying [Object object].

@mpen 2014-06-10 21:28:25

@ChristianOudard That would be really easy to polyfill, no? Would make your code a bit more legible.

@minhajul 2015-10-16 16:08:52

this will not work when the text node is deep nested inside other elements, because the contents() method only returns the immediate children nodes,

@minhajul 2015-10-16 16:14:49

@Jauco, nope, not enough! as .contents() returns only the immediate children nodes

@Rahen Rangan 2011-06-22 18:36:14

if you want to strip all tags, then try this


var rtag=/<.*?[^>]>/g;
return this.replace(rtag,'');


var newText=$('selector').html().stripTags();

@colllin 2011-04-20 14:52:19

If you can make the assumption that all children are either Element Nodes or Text Nodes, then this is one solution.

To get all child text nodes as a jquery collection:


To get a copy of the original element with non-text children removed:


@colllin 2011-04-20 14:58:47

Just noticed Tim Down's comment on another answer. This solution only gets the direct children, not all descendents.

Related Questions

Sponsored Content

38 Answered Questions

98 Answered Questions

[SOLVED] How can I remove a specific item from an array?

  • 2011-04-23 22:17:18
  • Walker
  • 7148861 View
  • 8664 Score
  • 98 Answer
  • Tags:   javascript arrays

43 Answered Questions

[SOLVED] Is there an "exists" function for jQuery?

  • 2008-08-27 19:49:41
  • Jake McGraw
  • 774619 View
  • 2820 Score
  • 43 Answer
  • Tags:   javascript jquery

59 Answered Questions

[SOLVED] How do I check if an element is hidden in jQuery?

46 Answered Questions

[SOLVED] How do I remove a property from a JavaScript object?

39 Answered Questions

[SOLVED] Add table row in jQuery

57 Answered Questions

[SOLVED] How do I copy to the clipboard in JavaScript?

28 Answered Questions

[SOLVED] What does "use strict" do in JavaScript, and what is the reasoning behind it?

3 Answered Questions

34 Answered Questions

Sponsored Content