Pagination fuction in shortcode always displays on top

Your output is expected, but before I come to the solution and why’s, you have a couple of issues here

  • You are either using query_posts or nullifying the main query global which you should never do. The give away is your loop (if (have_posts()) : while (have_posts()) : the_post();) and the use of wp_reset_query();. You should be using a proper instance of WP_Query and adjust your pagination function accordingly. I have recently written a pagination function which does the same as your pagination function but which works with custom queries, main query and static front pages. Check it out here

  • Your function name (if using PSR coding standards) should be camelCase. WordPress still does not use PSR coding standards as yet. WordPress coding standards states that the function names should be all lowercase and words separated by underscores.

  • Never ever use extract(). This function is unreliable and is extremely hard to debug should it fail. For a couple of reasons apart from these, extract() was completely removed from core a while ago. See this ticket: trac ticket #22400

  • global $post; is unnecessary, I cannot see the use of it inside your code. Also $postformat = get_post_format(); will return a undefined variable notice. Turn on debug to see the debug errors

  • You are echoing your pagination function inside your shortcode. Shortcodes should never echo output, it should always be returned. Content that is echoed gives unexpected output like you are seeing where your pagination is displaying on top of your posts and not where you have placed it

  • Don’t use syntax like ; and endwhile or endif. Although valid php, it is hard to debug and code editors does not support this syntax. Rather use the old school curlies {} which is widely supported by code editors and is really easy to debug

EDIT

There is one or two extra problems I see here

  • next_posts_link() and previous_posts_link() echo their output, which is unappropriate here. Use the get_ prefix functions and concatenate them to yor variable which will be returned later

  • if ( isset( $additional_loop ) ){ this check is unnecessary as $additional_loop is your query variable which will always exists

Here is your code. I have made a few changes, others I have just commented. Please see my comments in code for further details

/*
 * Your function name is wrong, should be something like recent_blog
 */
function RecentBlog($atts, $content = null) {

    /* 
     * This is the proper set up of attributes. 
     * Use your attributes as follow: $attributes['comments']
     */
    $attributes = shortcode_atts(array(
        "comments"       => 'true',
        "date"           => 'true',
        "columns"        => '4',
        "limit"          => '-1',
        "title"          => 'true',
        "description"    => 'true',
        "cat_slug"       => '',
        "post_type"      => '',
        "excerpt_length" => '15',
        "readmore_text"  => '',
        "pagination"     => 'false'
    ), $atts);

    // I don't know about this, does not make sense
    global $post;

    // This is a bug, will produce a undefined variable notice. Turn debug on
    $postformat = get_post_format();

    ....

    if ( get_query_var('paged') ) {
        $paged = get_query_var('paged');
    } elseif ( get_query_var('page') ) {
        $paged = get_query_var('page');
    } else {
        $paged = 1;
    }

    // Your query here is wrong. Use WP_Query. Something like below

    $args = array(
        'posts_per_page' => $attributes['limit'],
        'category_name'  => $attributes['cat_slug'],
        'paged'          => $paged,
        //etc
    );
    $additional_loop = new WP_Query( $args );

    /*
     * Give each condition its own line. Also use curlies
     * Do not use `query_posts`. Set your loop to your query variable which seems
     * to be $additional_loop
     */
    if ($additional_loop->have_posts()) { 
        while ($additional_loop->have_posts()) {
            $additional_loop->the_post();

            $postformat = get_post_format();
                if( $postformat == "" ) $postformat="standard";

            $protected = "";

            $portfoliogrid .= '<div class="' . $column_no . ' post grid-post post-' . $postformat . '">';

                .....////

                $portfoliogrid .= '<div class="summary-info">';
                    .......
                $portfoliogrid .='</div>';

                /*
                 * Change all your attributes accordingly like I did below
                 * $title becomes $attributes['title']
                 * $description becomes $attributes['description']
                 */
                // If either of title and description needs to be displayed.
                if ( $attributes['title'] == "true" || $attributes['description'] == "true" ) {

                    $portfoliogrid .='<div class="work-details">';
                    ......
                    $portfoliogrid .='</div>';

                }

            $portfoliogrid .='</div>';

        }
    }

    /*
     * Change attributes accordingly to new attribute syntax without extract()
     * $pagination becomes $attributes['pagination']
     */
    if ( $attributes['pagination'] == "true" ){

        /*
         * Do you really need this condition. $additional_loop will always exists as this is your
         * set query variable
         */
        if ( isset( $additional_loop ) ){

            /*
             * Do not echo your output. Concatenate it to your variable to be returned later
             */
            $portfoliogrid .= suareztheme_pagination( $additional_loop->max_num_pages );

        } else {

            $portfoliogrid .= suareztheme_pagination();

        }

        /*
         * This seems all wrong. Don't know what is your intention here
         */
        if ( function_exists("suareztheme_pagination") ) {

        } else {

            /*
             * use the get_* prefix functions here and return them
             */
            $portfoliogrid .= get_next_posts_link('&laquo;&laquo; Older Posts');
            $portfoliogrid .= get_previous_posts_link('Newer Posts &raquo;&raquo;');

        }

    }
    /*
     * This should be wp_reset_postdata(). wp_reset_query() is used with query_posts
     * which you should never use*/
    wp_reset_query();
    return $portfoliogrid;
}
/*
 * Change function call back name according to your new function name
 */
add_shortcode( "recentblog", "RecentBlog" );

EDIT 2

Here is a complete example of your your shortcode should look like. Before I paste the code, just a few notes

  • I have not tested the code. You should not paste it in a live site directly, first test it on a local install

  • I have commented everything that I have done, before you test the code, compare my code and your code and make sure you know what I have done. You can remove the comments if you wish after you have done

  • One VERY IMPORTANT NOTE, comments_popup_link echos its output, which does not work in a shortcode. There is no alternative function yet in WordPress which returns its output. One of the site members, @Otto has written a custom function get_comments_popup_link (which you can find on his personal site) to overcome this issue. You will need to copy and paste that function in your functions.php before you add your shortcode. Here is the function from @Otto

    /**
     * Modifies WordPress's built-in comments_popup_link() function to return a string instead of echo comment results
     */
    function get_comments_popup_link( $zero = false, $one = false, $more = false, $css_class="", $none = false ) {
        global $wpcommentspopupfile, $wpcommentsjavascript;
    
        $id = get_the_ID();
    
        if ( false === $zero ) $zero = __( 'No Comments' );
        if ( false === $one ) $one = __( '1 Comment' );
        if ( false === $more ) $more = __( '% Comments' );
        if ( false === $none ) $none = __( 'Comments Off' );
    
        $number = get_comments_number( $id );
    
        $str="";
    
        if ( 0 == $number && !comments_open() && !pings_open() ) {
            $str="<span" . ((!empty($css_class)) ? ' class="' . esc_attr( $css_class ) . '"' : '') . '>' . $none . '</span>';
            return $str;
        }
    
        if ( post_password_required() ) {
            $str = __('Enter your password to view comments.');
            return $str;
        }
    
        $str="<a href="";
        if ( $wpcommentsjavascript ) {
            if ( empty( $wpcommentspopupfile ) )
                $home = home_url();
            else
                $home = get_option('siteurl');
            $str .= $home . "https://wordpress.stackexchange.com/" . $wpcommentspopupfile . '?comments_popup=' . $id;
            $str .= '" onclick="wpopen(this.href); return false"';
        } else { // if comments_popup_script() is not in the template, display simple comment link
            if ( 0 == $number )
                $str .= get_permalink() . '#respond';
            else
                $str .= get_comments_link();
            $str .= '"';
        }
    
        if ( !empty( $css_class ) ) {
            $str .= ' class="'.$css_class.'" ';
        }
        $title = the_title_attribute( array('echo' => 0 ) );
    
        $str .= apply_filters( 'comments_popup_link_attributes', '' );
    
        $str .= ' title="' . esc_attr( sprintf( __('Comment on %s'), $title ) ) . '">';
        $str .= get_comments_number_str( $zero, $one, $more );
        $str .= '</a>';
    
        return $str;
    }
    

Finally, here is your new (hopefully working) shortcode. I hope this solves some of your main issues 🙂

function recent_blog_shortcode($atts, $content = null) {

    /*
     * Do not wrap booleans and integers in quotes. If they are wrapped in quotes
     * they are interpreted as string values. 'true' !== true. First one is a string
     * while the second is a boolean
     *
     * Also, stick with single quotes (or double quotes), don't mix them, it is confusing
     * when it comes to debugging
     *
     * Do not use extract() as I have already explained
     */
    $attributes = shortcode_atts(array(
        'comments'       => true,
        'date'           => true,
        'vote'           => true,
        'columns'        => 4,
        'limit'          => -1,
        'title'          => true,
        'description'    => true,
        'cat_slug'       => '',
        'post_type'      => '',
        'excerpt_length' => 15,
        'readmore_text'  => '',
        'pagination'     => false
    ), $atts);

    /*
     * Stay out of the global scope. It is not very good practise.
     */
    global $post;
           //$portfoliogrid, <-- Bad name for a global variable, too easy
           //$column_no; <-- Bad name for a global variable, too easy

    /*
     * As precaution, check if $attributes['columns'] is not empty. Also set the $columns variable from 
     * our attributes
     */
    $columns = $attributes['columns'];

    /*
     * Sets the variables to make sure your two variables is not undefined (bug)
     * if $columns are empty
     */
    $column_no = '';
    $portfolioImage_type="",

    if ( $columns ) {

        if ( $columns == 4 ) {

            $column_no = 'col-md-3';
            $portfolioImage_type="gridblock-medium";

        }

        if ( $columns == 3 ) {

            $column_no = 'col-md-4';
            $portfolioImage_type="gridblock-large";

        }

        if ( $columns == 2 ) {

            $column_no = 'col-md-6';
            $portfolioImage_type="gridblock-large";

        }

        if ( $columns == 1 ) {

            $column_no = $columns;
            $portfolioImage_type="gridblock-full";

        }

    }

    /*
     * Again, stay with single quotes OR double quotes, going with single quotes
     */
    $postformats="";
    $terms="";
    $portfoliogrid = '';

    /*
     * Great job here 
     */
    if ( get_query_var('paged') ) {
        $paged = get_query_var('paged');
    } elseif ( get_query_var('page') ) {
        $paged = get_query_var('page');
    } else {
        $paged = 1;
    }

    /*
     * Change $post_type to our new attribute system, $attributes['post_type']
     * For ease, just set the variable $post_type to the attribute
     */
    $post_type = $attributes['post_type']; 

    if ( $post_type="" ) {

        $type_explode = explode( ',', $post_type );
        foreach ( $type_explode as $postformat ) {
            $count++;
            $postformat_slug = "post-format-" . $postformat;
            $terms[] .= $postformat_slug;
        }

        /*
         * Never ever use query_posts. It breaks page functionalities, pagination
         * and the main query. Very bad function to use, please avoid it. Use WP_Query
         * instead. 
         *
         * Make your arguments conditional, not your query. Change our variables here to use the
         * new attribute system without extract()
         */
        $args = array(
            'category_name'  => $attributes['cat_slug'],
            'posts_per_page' => $attributes['limit'],
            'paged'          => $paged,
            'tax_query'      => array(
                array(
                    'taxonomy' => 'post_format',
                    'field'    => 'slug',
                    'terms'    => $terms
                )
            )
        );

    } else {

        /*
         * Never ever use query_posts. It breaks page functionalities, pagination
         * and the main query. Very bad function to use, please avoid it. Use WP_Query
         * instead. 
         *
         * Make your arguments conditional, not your query. Change our variables here to use the
         * new attribute system without extract()
         */
        $args = array(
            'category_name'  => $attributes['cat_slug'],
            'paged'          => $paged,
            'posts_per_page' => $attributes['limit'],
        );

    }

    /*
     * Create our query with WP_Query, very good practice
     */
    $q = new WP_Query( $args );

    $suareztheme_pagestyle = get_post_meta( $post->ID, 'pagestyle', true );

    /*
     * Each condition on a new line, and use curlies
     * Set our loop to our custom query. VERY IMPORTANT
     */
    if ( $q->have_posts() ) {
        while ( $q->have_posts() ) {

            $q->the_post();

            $postformat = get_post_format();
            if( $postformat == '' ) 
                $postformat="standard";

            $protected = '';

            $portfoliogrid .= '<div class="' . $column_no . ' post grid-post post-' . $postformat . '">';

                //if Password Required
                /* 
                 * Where is MTHEME_PATH defined, this is a bug if MTHEME_PATH does not exists
                 */
                if ( post_password_required() ) {
                    $protected = ' portfolio-protected'; 
                    $iconclass="";
                    $portfoliogrid .= '<a class="grid-blank-element '. $protected .' gridblock-image-link gridblock-columns" title="'. get_the_title() .'" href="'.get_permalink().'" >';
                    $portfoliogrid .= '<span class="grid-blank-status"><i class="icon-lock icon-2x"></i></span>';
                    $portfoliogrid .= '<div class="portfolio-protected"><img src="'. MTHEME_PATH .'/images/icons/blank-grid.png" /></div>';
                } else {
                    // if ( ! has_post_thumbnail() ) {
                    //  $portfoliogrid .= '<a class="grid-blank-element '.$protected.' gridblock-image-link gridblock-columns" title="'.get_the_title().'" href="'.get_permalink().'" >';
                    //  $portfoliogrid .= '<span class="grid-blank-status"><i class="'.$postformat_icon.' icon-2x"></i></span>';
                    //  $portfoliogrid .= '<div class="gridblock-protected"><img src="'.MTHEME_PATH.'/images/icons/blank-grid.png" /></div>';
                    // }

                    if ( has_post_thumbnail() ) {

                        //Make sure it's not a slideshow
                        //Switch check for Linked Type
                        $portfoliogrid .= '<a class="post-image" href="' . get_permalink() . '" rel="bookmark" title="' . get_the_title(). '">';

                        // If it aint slideshow then display a background. Otherwise one is active in slideshow thumbnails.
                        // Custom Thumbnail
                        // Display Image
                        /*
                         * suareztheme_display_post_image is a custom function. You should first check
                         * if it exists, because if it does not, you will crash your site-content
                         */
                        if ( function_exists( 'suareztheme_display_post_image' ) { 
                            $portfoliogrid .= suareztheme_display_post_image (
                                get_the_ID(),
                                $have_image_url="",
                                $link = false,
                                $type = $portfolioImage_type,
                                $imagetitle="",
                                $class="preload-image displayed-image"
                            );
                        }
                    /*
                     * else if should be elseif. else if !== elseif
                     */
                    } elseif ( $postformat == 'gallery' ){

                        /*
                         * the_post_format_gallery_content() is a custom function. You should first check
                         * if it exists, because if it does not, you will crash your site-content
                         */
                        if ( function_exists( 'the_post_format_gallery_content' ) 
                            $portfoliogrid .= the_post_format_gallery_content();

                    } else if ( $postformat == 'video' || $postformat == 'audio' ){

                        $embed_audio_code = get_post_meta($post->ID, 'audio_embed_code', true);
                        $embed_video_code = get_post_meta($post->ID, 'video_embed_code', true);

                        if ( $embed_audio_code ){

                            $portfoliogrid .= '<div>' . $embed_audio_code . '</div>';

                        } else {

                            $portfoliogrid .= '<div>' . $embed_video_code . '</div>';

                        }

                    } else if ( $postformat == "quote" ){

                        $portfoliogrid .= '<div class="quote">';

                            $portfoliogrid .= '<p class="quote-say">' . get_post_meta($post->ID, 'meta_quote', true) . '</p>';

                            if ( $quote_author != '' ){

                                $portfoliogrid .= '<p class="quote-author">- ' . get_post_meta($post->ID, 'meta_quote_author', true) . '</p>';

                            }

                        $portfoliogrid .= '</div>';

                    } else {

                        $portfoliogrid .= '<a class="'. $protected .' gridblock-image-link gridblock-columns" title="'. get_the_title() .'" href="'. get_permalink() .'" >';
                        $portfoliogrid .= '<div class="post-nothumbnail"></div>';

                    } 
                }

                $portfoliogrid .= '</a>';

                /*
                 * Don't wrap booleans in quotes as described earlier
                 * Also set our new attribute scheme
                 */
                if ( $attribute['date'] == true ){

                    $portfoliogrid .= '<span class="date">' . esc_html( get_the_date('F jS, Y') ) . '</span>';

                }

                // If either of title and description needs to be displayed.
                if ( $title == "true" ) {

                    $portfoliogrid .='<div class="entry-header">';

                        $hreflink = esc_url( get_permalink() );

                        /*
                         * Don't wrap booleans in quotes as described earlier
                         * Also set our new attribute scheme
                         */
                        if ( $attribute['title'] == true && $postformat == 'link' ) {

                            $portfoliogrid .='<h4><a href="' . esc_url( get_post_meta( $post->ID, 'meta_link', true ) ) . '" rel="bookmark" title="' . get_the_title() . '">'. get_the_title() .'</a></h4>'; 

                        } else {

                            $portfoliogrid .='<h4><a href="' . $hreflink . '" rel="bookmark" title="' . get_the_title() . '">'. get_the_title() .'</a></h4>'; 

                        }

                    $portfoliogrid .='</div>';

                }

                /*
                 * Don't wrap booleans in quotes as described earlier
                 * Also set our new attribute scheme
                 */
                if ( $attribute['description'] == true ) {

                    /*
                     * Set our new attribute scheme
                     *
                     * suareztheme_excerpt_limit is a custom function. You should first check
                     * if it exists, because if it does not, you will crash your site-content
                     */
                    if ( function_exists( 'suareztheme_excerpt_limit' ) { 
                        $summary_content = esc_attr( suareztheme_excerpt_limit($attribute['excerpt_length']) );

                        $portfoliogrid .= '<div class="entry-content"><p>'. $summary_content .'</p></div>';
                    }

                }   

                /*
                 * Don't wrap booleans in quotes as described earlier
                 * Set our new attribute scheme
                 */
                if (   $attribute['readmore_text'] != '' 
                    || $attribute['comments']      == true 
                    || $attribute['vote']          == true 
                ) {

                    $portfoliogrid .= '<div class="entry-links">';
                        $portfoliogrid .= '<div class="entry-links-left">';
                            /*
                             * dot_irecommendthis() is a custom function. You should first check
                             * if it exists, because if it does not, you will crash your site-content
                             *
                             * Make sure dot_irecommendthis() returns its output, and not echo
                             */
                            if ( function_exists( 'dot_irecommendthis' )  
                                $portfoliogrid .= dot_irecommendthis();

                            /*
                             * comments_popup_link echos its output. There is no version that returns it output
                             * Needs a workaround. See this article by Otto and use that function as a workaround
                             * Link to article http://www.thescubageek.com/code/wordpress-code/add-get_comments_popup_link-to-wordpress/
                             * Replace comments_popup_link with get_comments_popup_link and first check if the function
                             * exists. Remember to copy the function from the link from Otto to functions.php
                             */
                            if ( function_exists( 'get_comments_popup_link' )  
                                $portfoliogrid .= '<span class="entry-comments"><i class="fa fa-comments"></i>' . get_comments_popup_link('Leave a Reply', '1', '%') . '</span>';

                        $portfoliogrid .= '</div>';

                        $portfoliogrid .= '<div class="entry-links-right">';
                            $portfoliogrid .= '<a href="' . $hreflink . '" class="btn-readmore">' . $attribute['readmore_text'] . ' <i class="fa fa-chevron-right"></i></a>';
                        $portfoliogrid .= '</div>';
                    $portfoliogrid .= '</div>';

                }

            $portfoliogrid .='</div>';

        }
    }

    /*
     * Don't wrap booleans in quotes as described earlier
     * Set our new attribute scheme
     */
    if ( $attribute['pagination'] == true ){

        $portfoliogrid .= '<div class="col-md-12">';

            /* 
             * Remember to set our maximum pages for the custom query*/
            if ( function_exists( 'suareztheme_pagination' ) ) {

                $portfoliogrid .= suareztheme_pagination( $q->max_num_pages );

            } else {

                $portfoliogrid .= get_next_posts_link( '&laquo;&laquo; Older Posts', $q->max_num_pages );
                $portfoliogrid .= get_previous_posts_link( 'Newer Posts &raquo;&raquo;' );

            }

        $portfoliogrid .= '</div>';

    }

    /*
     * Do not use wp_reset_query(), it is used with query_posts which you MUST NEVER use
     * Use wp_reset_postdata()
     */
    wp_reset_postdata();

    return $portfoliogrid;
}

add_shortcode( 'recentblog', 'recent_blog_shortcode' );